diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..0d20b6487c61e7d1bde93acf4a14b7a89083a16d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/Android.bp b/Android.bp index ac00435be8de0020ddf599bdcd16cf2bd96de201..d5059fb901d6a1632de75b57dbabf2b520c8a5c2 100644 --- a/Android.bp +++ b/Android.bp @@ -18,12 +18,30 @@ // Using Chrome header files directly could cause -Wunused-parameter errors, // and this is workaround. Please find the document in include_generator.py // for details. +// Note: gensrcs does not support exclude_srcs, so filegroup rule is +// introduced. +filegroup { + name: "libchrome-include-sources", + srcs: [ + "base/**/*.h", + "build/**/*.h", + "components/**/*.h", + "device/**/*.h", + "testing/**/*.h", + "third_party/**/*.h", + "ui/**/*.h", + ], + exclude_srcs: [ + "base/android/**/*", + ], +} + gensrcs { name: "libchrome-include", cmd: "$(location libchrome_tools/include_generator.py) $(in) $(out)", tool_files: ["libchrome_tools/include_generator.py"], export_include_dirs: ["."], - srcs: ["**/*.h"], + srcs: [":libchrome-include-sources"], output_extension: "h", } @@ -39,10 +57,6 @@ cc_defaults { "-Wno-missing-field-initializers", "-Wno-unused-parameter", ], - include_dirs: [ - "external/valgrind/include", - "external/valgrind", - ], // Note: Although the generated header files are exported here, in building // libchrome, "." has priority (unlike building projects using libchrome), @@ -80,6 +94,8 @@ libchromeCommonSrc = [ "base/at_exit.cc", "base/base64.cc", "base/base64url.cc", + "base/base_paths.cc", + "base/base_paths_posix.cc", "base/base_switches.cc", "base/bind_helpers.cc", "base/build_time.cc", @@ -92,9 +108,9 @@ libchromeCommonSrc = [ "base/debug/debugger.cc", "base/debug/debugger_posix.cc", "base/debug/dump_without_crashing.cc", + "base/debug/proc_maps_linux.cc", "base/debug/profiler.cc", "base/debug/stack_trace.cc", - "base/debug/stack_trace_posix.cc", "base/debug/task_annotator.cc", "base/environment.cc", "base/feature_list.cc", @@ -154,6 +170,7 @@ libchromeCommonSrc = [ "base/metrics/sample_vector.cc", "base/metrics/sparse_histogram.cc", "base/metrics/statistics_recorder.cc", + "base/path_service.cc", "base/pending_task.cc", "base/pickle.cc", "base/posix/global_descriptors.cc", @@ -170,8 +187,6 @@ libchromeCommonSrc = [ "base/process/process_metrics.cc", "base/process/process_metrics_posix.cc", "base/process/process_posix.cc", - "base/profiler/scoped_profile.cc", - "base/profiler/scoped_tracker.cc", "base/profiler/tracked_time.cc", "base/rand_util.cc", "base/rand_util_posix.cc", @@ -235,51 +250,35 @@ libchromeCommonSrc = [ "base/time/time_posix.cc", "base/timer/elapsed_timer.cc", "base/timer/timer.cc", - "base/trace_event/category_registry.cc", - "base/trace_event/event_name_filter.cc", - "base/trace_event/heap_profiler_allocation_context.cc", - "base/trace_event/heap_profiler_allocation_context_tracker.cc", - "base/trace_event/heap_profiler_allocation_register.cc", - "base/trace_event/heap_profiler_allocation_register_posix.cc", - "base/trace_event/heap_profiler_event_filter.cc", - "base/trace_event/heap_profiler_heap_dump_writer.cc", - "base/trace_event/heap_profiler_stack_frame_deduplicator.cc", - "base/trace_event/heap_profiler_type_name_deduplicator.cc", - "base/trace_event/malloc_dump_provider.cc", - "base/trace_event/memory_allocator_dump.cc", - "base/trace_event/memory_allocator_dump_guid.cc", - "base/trace_event/memory_dump_manager.cc", - "base/trace_event/memory_dump_provider_info.cc", - "base/trace_event/memory_dump_request_args.cc", - "base/trace_event/memory_dump_scheduler.cc", - "base/trace_event/memory_dump_session_state.cc", - "base/trace_event/memory_infra_background_whitelist.cc", - "base/trace_event/memory_usage_estimator.cc", - "base/trace_event/process_memory_dump.cc", - "base/trace_event/process_memory_maps.cc", - "base/trace_event/process_memory_totals.cc", - "base/trace_event/trace_buffer.cc", - "base/trace_event/trace_config.cc", - "base/trace_event/trace_config_category_filter.cc", - "base/trace_event/trace_event_argument.cc", - "base/trace_event/trace_event_filter.cc", - "base/trace_event/trace_event_impl.cc", - "base/trace_event/trace_event_memory_overhead.cc", - "base/trace_event/trace_event_synthetic_delay.cc", - "base/trace_event/trace_log.cc", - "base/trace_event/trace_log_constants.cc", "base/tracked_objects.cc", "base/tracking_info.cc", + "base/unguessable_token.cc", "base/values.cc", "base/version.cc", "base/vlog.cc", + "device/bluetooth/bluetooth_advertisement.cc", + "device/bluetooth/bluetooth_uuid.cc", + "device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc", + "ui/gfx/geometry/insets.cc", + "ui/gfx/geometry/insets_f.cc", + "ui/gfx/geometry/point.cc", + "ui/gfx/geometry/point_conversions.cc", + "ui/gfx/geometry/point_f.cc", + "ui/gfx/geometry/rect.cc", + "ui/gfx/geometry/rect_f.cc", + "ui/gfx/geometry/size.cc", + "ui/gfx/geometry/size_conversions.cc", + "ui/gfx/geometry/size_f.cc", + "ui/gfx/geometry/vector2d.cc", + "ui/gfx/geometry/vector2d_f.cc", + "ui/gfx/range/range.cc", + "ui/gfx/range/range_f.cc", ] libchromeLinuxSrc = [ "base/files/file_path_watcher_linux.cc", "base/files/file_util_linux.cc", "base/memory/shared_memory_posix.cc", - "base/memory/shared_memory_tracker.cc", "base/posix/unix_domain_socket_linux.cc", "base/process/internal_linux.cc", "base/process/memory_linux.cc", @@ -296,10 +295,12 @@ libchromeLinuxSrc = [ libchromeLinuxGlibcSrc = [ "base/allocator/allocator_shim.cc", - "base/allocator/allocator_shim_default_dispatch_to_glibc.cc" + "base/allocator/allocator_shim_default_dispatch_to_glibc.cc", + "base/debug/stack_trace_posix.cc", ] libchromeAndroidSrc = [ + "base/debug/stack_trace_android.cc", "base/memory/shared_memory_android.cc", "base/sys_info_chromeos.cc", ] @@ -342,8 +343,21 @@ cc_library { // libchrome-crypto shared library for device // ======================================================== + +// Similar to libchrome, generate wrapped header files. See comments for +// libchrome-include for the details. +gensrcs { + name: "libchrome-crypto-include", + cmd: "$(location libchrome_tools/include_generator.py) $(in) $(out)", + tool_files: ["libchrome_tools/include_generator.py"], + export_include_dirs: ["."], + srcs: ["crypto/**/*.h"], + output_extension: "h", +} + cc_library_shared { name: "libchrome-crypto", + vendor_available: true, defaults: ["libchrome-defaults"], srcs: [ "crypto/openssl_util.cc", @@ -353,6 +367,9 @@ cc_library_shared { "crypto/sha2.cc", ], + generated_headers: ["libchrome-crypto-include"], + export_generated_headers: ["libchrome-crypto-include"], + shared_libs: [ "libchrome", "libcrypto", @@ -426,7 +443,6 @@ cc_test { "base/files/scoped_temp_dir_unittest.cc", "base/gmock_unittest.cc", "base/guid_unittest.cc", - "base/id_map_unittest.cc", "base/json/json_parser_unittest.cc", "base/json/json_reader_unittest.cc", "base/json/json_value_converter_unittest.cc", @@ -515,7 +531,6 @@ cc_test { "base/test/test_simple_task_runner.cc", "base/test/test_switches.cc", "base/test/test_timeouts.cc", - "base/test/trace_event_analyzer.cc", "base/threading/non_thread_safe_unittest.cc", "base/threading/platform_thread_unittest.cc", "base/threading/simple_thread_unittest.cc", @@ -531,19 +546,6 @@ cc_test { "base/time/time_unittest.cc", "base/timer/hi_res_timer_manager_unittest.cc", "base/timer/timer_unittest.cc", - "base/trace_event/event_name_filter_unittest.cc", - "base/trace_event/heap_profiler_allocation_context_tracker_unittest.cc", - "base/trace_event/heap_profiler_stack_frame_deduplicator_unittest.cc", - "base/trace_event/heap_profiler_type_name_deduplicator_unittest.cc", - "base/trace_event/memory_allocator_dump_unittest.cc", - "base/trace_event/memory_dump_manager_unittest.cc", - "base/trace_event/memory_usage_estimator_unittest.cc", - "base/trace_event/process_memory_dump_unittest.cc", - "base/trace_event/trace_config_unittest.cc", - "base/trace_event/trace_event_argument_unittest.cc", - "base/trace_event/trace_event_filter_test_utils.cc", - "base/trace_event/trace_event_synthetic_delay_unittest.cc", - "base/trace_event/trace_event_unittest.cc", "base/tracked_objects_unittest.cc", "base/tuple_unittest.cc", "base/values_unittest.cc", @@ -551,6 +553,7 @@ cc_test { "base/vlog_unittest.cc", "testing/multiprocess_func_list.cc", "testrunner.cc", + "ui/gfx/range/range_unittest.cc", ], cflags: ["-DUNIT_TEST"], @@ -575,3 +578,393 @@ cc_test { }, }, } + +filegroup { + name: "libmojo_mojom_files", + srcs: [ + "ipc/ipc.mojom", + "mojo/common/file.mojom", + "mojo/common/file_path.mojom", + "mojo/common/string16.mojom", + "mojo/common/text_direction.mojom", + "mojo/common/time.mojom", + "mojo/common/unguessable_token.mojom", + "mojo/common/values.mojom", + "mojo/common/version.mojom", + "mojo/public/interfaces/bindings/interface_control_messages.mojom", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom", + "ui/gfx/geometry/mojo/geometry.mojom", + "ui/gfx/range/mojo/range.mojom", + ], +} + +filegroup { + name: "libmojo_mojo_sources", + srcs: [ + "mojo/**/*.cc", + ], + exclude_srcs: [ + // Unused in Chrome. Looks like mistakenly checked in. + // TODO(hidehiko): Remove this after the file is removed in Chrome + // repository. http://crrev.com/c/644531 + "mojo/public/cpp/system/message.cc", + + // No WTF support. + "mojo/public/cpp/bindings/lib/string_traits_wtf.cc", + + // Exclude windows/mac/ios files. + "**/*_win.cc", + "mojo/edk/system/mach_port_relay.cc", + + // Exclude js binding related files. + "mojo/edk/js/**/*", + "mojo/public/js/**/*", + + // Exclude tests. + // Note that mojo/edk/embedder/test_embedder.cc needs to be included + // for Mojo support. cf) b/62071944. + "**/*_unittest.cc", + "**/*_unittests.cc", + "**/*_perftest.cc", + "mojo/android/javatests/**/*", + "mojo/edk/system/core_test_base.cc", + "mojo/edk/system/test_utils.cc", + "mojo/edk/test/**/*", + "mojo/public/c/system/tests/**/*", + "mojo/public/cpp/bindings/tests/**/*", + "mojo/public/cpp/system/tests/**/*", + "mojo/public/cpp/test_support/**/*", + "mojo/public/tests/**/*", + ], +} + +// Python in Chrome repository requires still Python 2. +python_defaults { + name: "libmojo_scripts", + version: { + py2: { + enabled: true, + }, + py3: { + enabled: false, + }, + }, +} + +python_binary_host { + name: "jni_generator", + main: "base/android/jni_generator/jni_generator.py", + srcs: [ + "base/android/jni_generator/jni_generator.py", + "build/**/*.py", + "third_party/catapult/devil/devil/**/*.py", + ], + defaults: ["libmojo_scripts"], +} + +python_binary_host { + name: "mojom_bindings_generator", + main: "mojo/public/tools/bindings/mojom_bindings_generator.py", + srcs: [ + "mojo/public/tools/bindings/**/*.py", + "build/**/*.py", + "third_party/catapult/devil/**/*.py", + "third_party/jinja2/**/*.py", + "third_party/markupsafe/**/*.py", + "third_party/ply/**/*.py", + ], + data: [ + "mojo/public/tools/bindings/generators/cpp_templates/*.tmpl", + "mojo/public/tools/bindings/generators/java_templates/*.tmpl", + "mojo/public/tools/bindings/generators/js_templates/*.tmpl", + ], + defaults: ["libmojo_scripts"], +} + +cc_prebuilt_binary { + name: "mojom_source_generator_sh", + srcs: ["libchrome_tools/mojom_source_generator.sh"], + host_supported: true, +} + +python_binary_host { + name: "mojom_generate_type_mappings", + main: "libchrome_tools/mojom_generate_type_mappings.py", + srcs: [ + "build/gn_helpers.py", + "libchrome_tools/mojom_generate_type_mappings.py", + "mojo/public/tools/bindings/generate_type_mappings.py", + ], + defaults: ["libmojo_scripts"], +} + +genrule { + name: "libmojo_common_custom_types__type_mappings", + cmd: "$(location mojom_generate_type_mappings)" + + " --output=$(out)" + + " $(in)", + + tools: ["mojom_generate_type_mappings"], + + srcs: [ + "mojo/common/file.typemap", + "mojo/common/file_path.typemap", + "mojo/common/string16.typemap", + "mojo/common/text_direction.typemap", + "mojo/common/time.typemap", + "mojo/common/unguessable_token.typemap", + "mojo/common/values.typemap", + "mojo/common/version.typemap", + ], + out: ["common_custom_types__type_mappings"], +} + +genrule { + name: "libmojo_mojom_headers", + cmd: "$(location mojom_source_generator_sh)" + + " --mojom_bindings_generator=$(location mojom_bindings_generator)" + + " --package=external/libchrome" + + " --output_dir=$(genDir)" + + " --bytecode_path=$(genDir)" + + " --typemap=$(location common_custom_types__type_mappings)" + + " --generators=c++" + + " --use_new_wrapper_types" + + " $(in)", + + tools: [ + "mojom_bindings_generator", + "mojom_source_generator_sh", + ], + + tool_files: [":libmojo_common_custom_types__type_mappings"], + + srcs: [":libmojo_mojom_files"], + + out: [ + "ipc/ipc.mojom.h", + "ipc/ipc.mojom-shared.h", + "ipc/ipc.mojom-shared-internal.h", + "mojo/common/file.mojom.h", + "mojo/common/file.mojom-shared.h", + "mojo/common/file.mojom-shared-internal.h", + "mojo/common/file_path.mojom.h", + "mojo/common/file_path.mojom-shared.h", + "mojo/common/file_path.mojom-shared-internal.h", + "mojo/common/string16.mojom.h", + "mojo/common/string16.mojom-shared.h", + "mojo/common/string16.mojom-shared-internal.h", + "mojo/common/text_direction.mojom.h", + "mojo/common/text_direction.mojom-shared.h", + "mojo/common/text_direction.mojom-shared-internal.h", + "mojo/common/time.mojom.h", + "mojo/common/time.mojom-shared.h", + "mojo/common/time.mojom-shared-internal.h", + "mojo/common/unguessable_token.mojom.h", + "mojo/common/unguessable_token.mojom-shared.h", + "mojo/common/unguessable_token.mojom-shared-internal.h", + "mojo/common/values.mojom.h", + "mojo/common/values.mojom-shared.h", + "mojo/common/values.mojom-shared-internal.h", + "mojo/common/version.mojom.h", + "mojo/common/version.mojom-shared.h", + "mojo/common/version.mojom-shared-internal.h", + "mojo/public/interfaces/bindings/interface_control_messages.mojom.h", + "mojo/public/interfaces/bindings/interface_control_messages.mojom-shared.h", + "mojo/public/interfaces/bindings/interface_control_messages.mojom-shared-internal.h", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom.h", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom-shared.h", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom-shared-internal.h", + "ui/gfx/geometry/mojo/geometry.mojom.h", + "ui/gfx/geometry/mojo/geometry.mojom-shared.h", + "ui/gfx/geometry/mojo/geometry.mojom-shared-internal.h", + "ui/gfx/range/mojo/range.mojom.h", + "ui/gfx/range/mojo/range.mojom-shared.h", + "ui/gfx/range/mojo/range.mojom-shared-internal.h", + ], +} + +genrule { + name: "libmojo_mojom_srcs", + cmd: "$(location mojom_source_generator_sh)" + + " --mojom_bindings_generator=$(location mojom_bindings_generator)" + + " --package=external/libchrome" + + " --output_dir=$(genDir)" + + " --bytecode_path=$(genDir)" + + " --typemap=$(location common_custom_types__type_mappings)" + + " --generators=c++" + + " --use_new_wrapper_types" + + " $(in)", + + tools: [ + "mojom_bindings_generator", + "mojom_source_generator_sh", + ], + + tool_files: [":libmojo_common_custom_types__type_mappings"], + + srcs: [":libmojo_mojom_files"], + + out: [ + "ipc/ipc.mojom.cc", + "ipc/ipc.mojom-shared.cc", + "mojo/common/file.mojom.cc", + "mojo/common/file.mojom-shared.cc", + "mojo/common/string16.mojom.cc", + "mojo/common/string16.mojom-shared.cc", + "mojo/common/text_direction.mojom.cc", + "mojo/common/text_direction.mojom-shared.cc", + "mojo/common/time.mojom.cc", + "mojo/common/time.mojom-shared.cc", + "mojo/common/unguessable_token.mojom.cc", + "mojo/common/unguessable_token.mojom-shared.cc", + "mojo/common/version.mojom.cc", + "mojo/common/version.mojom-shared.cc", + "mojo/public/interfaces/bindings/interface_control_messages.mojom.cc", + "mojo/public/interfaces/bindings/interface_control_messages.mojom-shared.cc", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom.cc", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom-shared.cc", + "ui/gfx/geometry/mojo/geometry.mojom.cc", + "ui/gfx/geometry/mojo/geometry.mojom-shared.cc", + "ui/gfx/range/mojo/range.mojom.cc", + "ui/gfx/range/mojo/range.mojom-shared.cc", + ], +} + +// TODO(hidehiko): Remove JNI for ContextUtils, after cleaning up the +// depended code. +genrule { + name: "libmojo_jni_headers", + cmd: "$(location libchrome_tools/jni_generator_helper.sh)" + + " --jni_generator=$(location jni_generator)" + + " --output_dir=$(genDir)/jni" + + " --includes=base/android/jni_generator/jni_generator_helper.h" + + " --ptr_type=long" + + " --native_exports_optional" + + " $(in)", + + tools: [ + "jni_generator", + ], + + tool_files: [ + "libchrome_tools/jni_generator_helper.sh", + ], + + srcs: [ + "base/android/java/src/org/chromium/base/BuildInfo.java", + "base/android/java/src/org/chromium/base/ContextUtils.java", + "mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java", + "mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java", + "mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java", + ], + + out: [ + "jni/BuildInfo_jni.h", + "jni/ContextUtils_jni.h", + "jni/BaseRunLoop_jni.h", + "jni/CoreImpl_jni.h", + "jni/WatcherImpl_jni.h", + ], +} + +cc_library_shared { + name: "libmojo", + vendor_available: true, + + generated_headers: [ + "libmojo_jni_headers", + "libmojo_mojom_headers", + ], + + generated_sources: [ + "libmojo_mojom_srcs", + ], + + export_generated_headers: [ + "libmojo_jni_headers", + "libmojo_mojom_headers", + ], + + srcs: [ + "base/android/build_info.cc", + "base/android/context_utils.cc", + "base/android/jni_android.cc", + "base/android/jni_string.cc", + "base/android/scoped_java_ref.cc", + "ipc/ipc_message.cc", + "ipc/ipc_message_attachment.cc", + "ipc/ipc_message_attachment_set.cc", + "ipc/ipc_message_utils.cc", + "ipc/ipc_mojo_handle_attachment.cc", + "ipc/ipc_mojo_message_helper.cc", + "ipc/ipc_mojo_param_traits.cc", + "ipc/ipc_platform_file_attachment_posix.cc", + ":libmojo_mojo_sources", + ], + + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wno-missing-field-initializers", + "-DMOJO_EDK_LEGACY_PROTOCOL", + ], + + // We also pass NO_ASHMEM to make base::SharedMemory avoid using it and prefer + // the POSIX versions. + cppflags: [ + "-Wno-sign-promo", + "-Wno-non-virtual-dtor", + "-Wno-ignored-qualifiers", + "-Wno-extra", + "-DNO_ASHMEM", + ], + + shared_libs: [ + "libevent", + "liblog", + "libchrome", + "libchrome-crypto", + ], + + header_libs: ["jni_headers"], + + export_include_dirs: ["."], +} + +genrule { + name: "libmojo_mojom_java_srcs", + cmd: "$(location mojom_source_generator_sh)" + + " --mojom_bindings_generator=$(location mojom_bindings_generator)" + + " --package=external/libchrome" + + " --output_dir=$(genDir)" + + " --bytecode_path=$(genDir)" + + " --typemap=$(location common_custom_types__type_mappings)" + + " --generators=java" + + " --use_new_wrapper_types" + + " --srcjar=$(out)" + + " $(in)", + + tools: [ + "mojom_bindings_generator", + "mojom_source_generator_sh", + ], + + tool_files: [":libmojo_common_custom_types__type_mappings"], + + srcs: [":libmojo_mojom_files"], + + out: ["libmojo_mojom.srcjar"], +} + +java_library { + name: "android.mojo", + + srcs: [ + ":libmojo_mojom_java_srcs", + "base/android/java/src/**/*.java", + "mojo/android/system/src/**/*.java", + "mojo/public/java/system/src/**/*.java", + "mojo/public/java/bindings/src/**/*.java", + ], +} diff --git a/OWNERS b/OWNERS index c474ccfb5e7d5c7d74c61ad23c7624b2884a83bc..8d673fa974a69ff09f4d77f085ec6879108cbf46 100644 --- a/OWNERS +++ b/OWNERS @@ -2,3 +2,7 @@ avakulenko@google.com derat@google.com deymo@google.com jorgelo@google.com + +# For Mojo changes. +lhchavez@google.com +hidehiko@google.com diff --git a/base/android/build_info.cc b/base/android/build_info.cc new file mode 100644 index 0000000000000000000000000000000000000000..869c703f3f10bc363152ed319404c520b8dfd70a --- /dev/null +++ b/base/android/build_info.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2012 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. + +#include "base/android/build_info.h" + +#include + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "jni/BuildInfo_jni.h" + +namespace { + +// We are leaking these strings. +const char* StrDupJString(const base::android::JavaRef& java_string) { + std::string str = ConvertJavaStringToUTF8(java_string); + return strdup(str.c_str()); +} + +} // namespace + +namespace base { +namespace android { + +struct BuildInfoSingletonTraits { + static BuildInfo* New() { + return new BuildInfo(AttachCurrentThread()); + } + + static void Delete(BuildInfo* x) { + // We're leaking this type, see kRegisterAtExit. + NOTREACHED(); + } + + static const bool kRegisterAtExit = false; +#if DCHECK_IS_ON() + static const bool kAllowedToAccessOnNonjoinableThread = true; +#endif +}; + +BuildInfo::BuildInfo(JNIEnv* env) + : device_(StrDupJString(Java_BuildInfo_getDevice(env))), + manufacturer_(StrDupJString(Java_BuildInfo_getDeviceManufacturer(env))), + model_(StrDupJString(Java_BuildInfo_getDeviceModel(env))), + brand_(StrDupJString(Java_BuildInfo_getBrand(env))), + android_build_id_(StrDupJString(Java_BuildInfo_getAndroidBuildId(env))), + android_build_fp_( + StrDupJString(Java_BuildInfo_getAndroidBuildFingerprint(env))), + gms_version_code_(StrDupJString(Java_BuildInfo_getGMSVersionCode(env))), + package_version_code_( + StrDupJString(Java_BuildInfo_getPackageVersionCode(env))), + package_version_name_( + StrDupJString(Java_BuildInfo_getPackageVersionName(env))), + package_label_(StrDupJString(Java_BuildInfo_getPackageLabel(env))), + package_name_(StrDupJString(Java_BuildInfo_getPackageName(env))), + build_type_(StrDupJString(Java_BuildInfo_getBuildType(env))), + sdk_int_(Java_BuildInfo_getSdkInt(env)), + java_exception_info_(NULL) {} + +// static +BuildInfo* BuildInfo::GetInstance() { + return Singleton::get(); +} + +void BuildInfo::SetJavaExceptionInfo(const std::string& info) { + DCHECK(!java_exception_info_) << "info should be set only once."; + java_exception_info_ = strndup(info.c_str(), 4096); +} + +void BuildInfo::ClearJavaExceptionInfo() { + delete java_exception_info_; + java_exception_info_ = nullptr; +} + +} // namespace android +} // namespace base diff --git a/base/android/build_info.h b/base/android/build_info.h new file mode 100644 index 0000000000000000000000000000000000000000..cce74f4e520a65c74736c8bbc8acb610ae515c94 --- /dev/null +++ b/base/android/build_info.h @@ -0,0 +1,145 @@ +// Copyright (c) 2012 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 BASE_ANDROID_BUILD_INFO_H_ +#define BASE_ANDROID_BUILD_INFO_H_ + +#include + +#include + +#include "base/base_export.h" +#include "base/macros.h" +#include "base/memory/singleton.h" + +namespace base { +namespace android { + +// This enumeration maps to the values returned by BuildInfo::sdk_int(), +// indicating the Android release associated with a given SDK version. +enum SdkVersion { + SDK_VERSION_JELLY_BEAN = 16, + SDK_VERSION_JELLY_BEAN_MR1 = 17, + SDK_VERSION_JELLY_BEAN_MR2 = 18, + SDK_VERSION_KITKAT = 19, + SDK_VERSION_KITKAT_WEAR = 20, + SDK_VERSION_LOLLIPOP = 21, + SDK_VERSION_LOLLIPOP_MR1 = 22, + SDK_VERSION_MARSHMALLOW = 23, + SDK_VERSION_NOUGAT = 24 +}; + +// BuildInfo is a singleton class that stores android build and device +// information. It will be called from Android specific code and gets used +// primarily in crash reporting. + +// It is also used to store the last java exception seen during JNI. +// TODO(nileshagrawal): Find a better place to store this info. +class BASE_EXPORT BuildInfo { + public: + + ~BuildInfo() {} + + // Static factory method for getting the singleton BuildInfo instance. + // Note that ownership is not conferred on the caller and the BuildInfo in + // question isn't actually freed until shutdown. This is ok because there + // should only be one instance of BuildInfo ever created. + static BuildInfo* GetInstance(); + + // Const char* is used instead of std::strings because these values must be + // available even if the process is in a crash state. Sadly + // std::string.c_str() doesn't guarantee that memory won't be allocated when + // it is called. + const char* device() const { + return device_; + } + + const char* manufacturer() const { + return manufacturer_; + } + + const char* model() const { + return model_; + } + + const char* brand() const { + return brand_; + } + + const char* android_build_id() const { + return android_build_id_; + } + + const char* android_build_fp() const { + return android_build_fp_; + } + + const char* gms_version_code() const { + return gms_version_code_; + } + + const char* package_version_code() const { + return package_version_code_; + } + + const char* package_version_name() const { + return package_version_name_; + } + + const char* package_label() const { + return package_label_; + } + + const char* package_name() const { + return package_name_; + } + + const char* build_type() const { + return build_type_; + } + + int sdk_int() const { + return sdk_int_; + } + + const char* java_exception_info() const { + return java_exception_info_; + } + + void SetJavaExceptionInfo(const std::string& info); + + void ClearJavaExceptionInfo(); + + private: + friend struct BuildInfoSingletonTraits; + + explicit BuildInfo(JNIEnv* env); + + // Const char* is used instead of std::strings because these values must be + // available even if the process is in a crash state. Sadly + // std::string.c_str() doesn't guarantee that memory won't be allocated when + // it is called. + const char* const device_; + const char* const manufacturer_; + const char* const model_; + const char* const brand_; + const char* const android_build_id_; + const char* const android_build_fp_; + const char* const gms_version_code_; + const char* const package_version_code_; + const char* const package_version_name_; + const char* const package_label_; + const char* const package_name_; + const char* const build_type_; + const int sdk_int_; + // This is set via set_java_exception_info, not at constructor time. + const char* java_exception_info_; + + DISALLOW_COPY_AND_ASSIGN(BuildInfo); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_BUILD_INFO_H_ diff --git a/base/android/context_utils.cc b/base/android/context_utils.cc new file mode 100644 index 0000000000000000000000000000000000000000..e2c4ed0b4b80ad2f95eba38d22a4f7da71ee2b69 --- /dev/null +++ b/base/android/context_utils.cc @@ -0,0 +1,53 @@ +// 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. + +#include "base/android/context_utils.h" + +#include + +#include "base/android/scoped_java_ref.h" +#include "base/lazy_instance.h" +#include "jni/ContextUtils_jni.h" + +using base::android::JavaRef; + +namespace base { +namespace android { + +namespace { + +// Leak the global app context, as it is used from a non-joinable worker thread +// that may still be running at shutdown. There is no harm in doing this. +base::LazyInstance>::Leaky + g_application_context = LAZY_INSTANCE_INITIALIZER; + +void SetNativeApplicationContext(JNIEnv* env, const JavaRef& context) { + if (env->IsSameObject(g_application_context.Get().obj(), context.obj())) { + // It's safe to set the context more than once if it's the same context. + return; + } + DCHECK(g_application_context.Get().is_null()); + g_application_context.Get().Reset(context); +} + +} // namespace + +const JavaRef& GetApplicationContext() { + DCHECK(!g_application_context.Get().is_null()); + return g_application_context.Get(); +} + +static void InitNativeSideApplicationContext( + JNIEnv* env, + const JavaParamRef& clazz, + const JavaParamRef& context) { + SetNativeApplicationContext(env, context); +} + +bool RegisterContextUtils(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/base/android/context_utils.h b/base/android/context_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..c5289f1d5e0a67655934c6fc7be3ff928f7f1bc9 --- /dev/null +++ b/base/android/context_utils.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef BASE_ANDROID_CONTEXT_UTILS_H_ +#define BASE_ANDROID_CONTEXT_UTILS_H_ + +#include + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" + +namespace base { +namespace android { + +// Gets a global ref to the application context set with +// InitApplicationContext(). Ownership is retained by the function - the caller +// must NOT release it. +BASE_EXPORT const JavaRef& GetApplicationContext(); + +bool RegisterContextUtils(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_CONTEXT_UTILS_H_ diff --git a/base/android/java/src/org/chromium/base/BuildInfo.java b/base/android/java/src/org/chromium/base/BuildInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..de4ad08a833905862c5a0ae412b5ce1a5c1b1fff --- /dev/null +++ b/base/android/java/src/org/chromium/base/BuildInfo.java @@ -0,0 +1,171 @@ +// Copyright 2012 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. + +package org.chromium.base; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build; +import android.os.StrictMode; + +import org.chromium.base.annotations.CalledByNative; + +/** + * BuildInfo is a utility class providing easy access to {@link PackageInfo} information. This is + * primarily of use for accessing package information from native code. + */ +public class BuildInfo { + private static final String TAG = "BuildInfo"; + private static final int MAX_FINGERPRINT_LENGTH = 128; + + /** + * BuildInfo is a static utility class and therefore shouldn't be instantiated. + */ + private BuildInfo() {} + + @CalledByNative + public static String getDevice() { + return Build.DEVICE; + } + + @CalledByNative + public static String getBrand() { + return Build.BRAND; + } + + @CalledByNative + public static String getAndroidBuildId() { + return Build.ID; + } + + /** + * @return The build fingerprint for the current Android install. The value is truncated to a + * 128 characters as this is used for crash and UMA reporting, which should avoid huge + * strings. + */ + @CalledByNative + public static String getAndroidBuildFingerprint() { + return Build.FINGERPRINT.substring( + 0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH)); + } + + @CalledByNative + public static String getDeviceManufacturer() { + return Build.MANUFACTURER; + } + + @CalledByNative + public static String getDeviceModel() { + return Build.MODEL; + } + + @CalledByNative + public static String getGMSVersionCode() { + String msg = "gms versionCode not available."; + try { + PackageManager packageManager = + ContextUtils.getApplicationContext().getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo("com.google.android.gms", 0); + msg = Integer.toString(packageInfo.versionCode); + } catch (NameNotFoundException e) { + Log.d(TAG, "GMS package is not found.", e); + } + return msg; + } + + @CalledByNative + public static String getPackageVersionCode() { + String msg = "versionCode not available."; + try { + PackageManager pm = ContextUtils.getApplicationContext().getPackageManager(); + PackageInfo pi = pm.getPackageInfo(getPackageName(), 0); + msg = ""; + if (pi.versionCode > 0) { + msg = Integer.toString(pi.versionCode); + } + } catch (NameNotFoundException e) { + Log.d(TAG, msg); + } + return msg; + } + + @CalledByNative + public static String getPackageVersionName() { + String msg = "versionName not available"; + try { + PackageManager pm = ContextUtils.getApplicationContext().getPackageManager(); + PackageInfo pi = pm.getPackageInfo(getPackageName(), 0); + msg = ""; + if (pi.versionName != null) { + msg = pi.versionName; + } + } catch (NameNotFoundException e) { + Log.d(TAG, msg); + } + return msg; + } + + @CalledByNative + public static String getPackageLabel() { + // Third-party code does disk read on the getApplicationInfo call. http://crbug.com/614343 + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + PackageManager packageManager = + ContextUtils.getApplicationContext().getPackageManager(); + ApplicationInfo appInfo = packageManager.getApplicationInfo( + getPackageName(), PackageManager.GET_META_DATA); + CharSequence label = packageManager.getApplicationLabel(appInfo); + return label != null ? label.toString() : ""; + } catch (NameNotFoundException e) { + return ""; + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + @CalledByNative + public static String getPackageName() { + if (ContextUtils.getApplicationContext() == null) { + return ""; + } + return ContextUtils.getApplicationContext().getPackageName(); + } + + @CalledByNative + public static String getBuildType() { + return Build.TYPE; + } + + /** + * Check if this is a debuggable build of Android. Use this to enable developer-only features. + */ + public static boolean isDebugAndroid() { + return "eng".equals(Build.TYPE) || "userdebug".equals(Build.TYPE); + } + + @CalledByNative + public static int getSdkInt() { + return Build.VERSION.SDK_INT; + } + + /** + * @return Whether the current device is running Android O release or newer. + */ + public static boolean isAtLeastO() { + return !"REL".equals(Build.VERSION.CODENAME) + && ("O".equals(Build.VERSION.CODENAME) || Build.VERSION.CODENAME.startsWith("OMR")); + } + + /** + * @return Whether the current app targets the SDK for at least O + */ + public static boolean targetsAtLeastO(Context appContext) { + return isAtLeastO() + && appContext.getApplicationInfo().targetSdkVersion + == Build.VERSION_CODES.CUR_DEVELOPMENT; + } +} diff --git a/base/android/java/src/org/chromium/base/ContextUtils.java b/base/android/java/src/org/chromium/base/ContextUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..448eff9b6a9f33ebf3ef4c3eecaa91711bd7d0a1 --- /dev/null +++ b/base/android/java/src/org/chromium/base/ContextUtils.java @@ -0,0 +1,115 @@ +// 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. + +package org.chromium.base; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; + +/** + * This class provides Android application context related utility methods. + */ +@JNINamespace("base::android") +@MainDex +public class ContextUtils { + private static final String TAG = "ContextUtils"; + private static Context sApplicationContext; + + /** + * Initialization-on-demand holder. This exists for thread-safe lazy initialization. + */ + private static class Holder { + // Not final for tests. + private static SharedPreferences sSharedPreferences = fetchAppSharedPreferences(); + } + + /** + * Get the Android application context. + * + * Under normal circumstances there is only one application context in a process, so it's safe + * to treat this as a global. In WebView it's possible for more than one app using WebView to be + * running in a single process, but this mechanism is rarely used and this is not the only + * problem in that scenario, so we don't currently forbid using it as a global. + * + * Do not downcast the context returned by this method to Application (or any subclass). It may + * not be an Application object; it may be wrapped in a ContextWrapper. The only assumption you + * may make is that it is a Context whose lifetime is the same as the lifetime of the process. + */ + public static Context getApplicationContext() { + return sApplicationContext; + } + + /** + * Initializes the java application context. + * + * This should be called exactly once early on during startup, before native is loaded and + * before any other clients make use of the application context through this class. + * + * @param appContext The application context. + */ + public static void initApplicationContext(Context appContext) { + // Conceding that occasionally in tests, native is loaded before the browser process is + // started, in which case the browser process re-sets the application context. + if (sApplicationContext != null && sApplicationContext != appContext) { + throw new RuntimeException("Attempting to set multiple global application contexts."); + } + initJavaSideApplicationContext(appContext); + } + + /** + * Initialize the native Android application context to be the same as the java counter-part. + */ + public static void initApplicationContextForNative() { + if (sApplicationContext == null) { + throw new RuntimeException("Cannot have native global application context be null."); + } + nativeInitNativeSideApplicationContext(sApplicationContext); + } + + /** + * Only called by the static holder class and tests. + * + * @return The application-wide shared preferences. + */ + private static SharedPreferences fetchAppSharedPreferences() { + return PreferenceManager.getDefaultSharedPreferences(sApplicationContext); + } + + /** + * This is used to ensure that we always use the application context to fetch the default shared + * preferences. This avoids needless I/O for android N and above. It also makes it clear that + * the app-wide shared preference is desired, rather than the potentially context-specific one. + * + * @return application-wide shared preferences. + */ + public static SharedPreferences getAppSharedPreferences() { + return Holder.sSharedPreferences; + } + + /** + * Occasionally tests cannot ensure the application context doesn't change between tests (junit) + * and sometimes specific tests has its own special needs, initApplicationContext should be used + * as much as possible, but this method can be used to override it. + * + * @param appContext The new application context. + */ + @VisibleForTesting + public static void initApplicationContextForTests(Context appContext) { + initJavaSideApplicationContext(appContext); + Holder.sSharedPreferences = fetchAppSharedPreferences(); + } + + private static void initJavaSideApplicationContext(Context appContext) { + if (appContext == null) { + throw new RuntimeException("Global application context cannot be set to null."); + } + sApplicationContext = appContext; + } + + private static native void nativeInitNativeSideApplicationContext(Context appContext); +} diff --git a/base/android/java/src/org/chromium/base/Log.java b/base/android/java/src/org/chromium/base/Log.java new file mode 100644 index 0000000000000000000000000000000000000000..399f16dfc158b9ee5e92dbedd3c955d36a5f681a --- /dev/null +++ b/base/android/java/src/org/chromium/base/Log.java @@ -0,0 +1,387 @@ +// 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. + +package org.chromium.base; + +import org.chromium.base.annotations.RemovableInRelease; + +import java.util.Locale; + +/** + * Utility class for Logging. + * + *

+ * Defines logging access points for each feature. They format and forward the logs to + * {@link android.util.Log}, allowing to standardize the output, to make it easy to identify + * the origin of logs, and enable or disable logging in different parts of the code. + *

+ *

+ * Usage documentation: {@code //docs/android_logging.md}. + *

+ */ +public class Log { + /** Convenience property, same as {@link android.util.Log#ASSERT}. */ + public static final int ASSERT = android.util.Log.ASSERT; + + /** Convenience property, same as {@link android.util.Log#DEBUG}. */ + public static final int DEBUG = android.util.Log.DEBUG; + + /** Convenience property, same as {@link android.util.Log#ERROR}. */ + public static final int ERROR = android.util.Log.ERROR; + + /** Convenience property, same as {@link android.util.Log#INFO}. */ + public static final int INFO = android.util.Log.INFO; + + /** Convenience property, same as {@link android.util.Log#VERBOSE}. */ + public static final int VERBOSE = android.util.Log.VERBOSE; + + /** Convenience property, same as {@link android.util.Log#WARN}. */ + public static final int WARN = android.util.Log.WARN; + + private static final String sTagPrefix = "cr_"; + private static final String sDeprecatedTagPrefix = "cr."; + + private Log() { + // Static only access + } + + /** Returns a formatted log message, using the supplied format and arguments.*/ + private static String formatLog(String messageTemplate, Object... params) { + if (params != null && params.length != 0) { + messageTemplate = String.format(Locale.US, messageTemplate, params); + } + + return messageTemplate; + } + + /** + * Returns a normalized tag that will be in the form: "cr_foo". This function is called by the + * various Log overrides. If using {@link #isLoggable(String, int)}, you might want to call it + * to get the tag that will actually be used. + * @see #sTagPrefix + */ + public static String normalizeTag(String tag) { + if (tag.startsWith(sTagPrefix)) return tag; + + // TODO(dgn) simplify this once 'cr.' is out of the repo (http://crbug.com/533072) + int unprefixedTagStart = 0; + if (tag.startsWith(sDeprecatedTagPrefix)) { + unprefixedTagStart = sDeprecatedTagPrefix.length(); + } + + return sTagPrefix + tag.substring(unprefixedTagStart, tag.length()); + } + + /** + * Returns a formatted log message, using the supplied format and arguments. + * The message will be prepended with the filename and line number of the call. + */ + private static String formatLogWithStack(String messageTemplate, Object... params) { + return "[" + getCallOrigin() + "] " + formatLog(messageTemplate, params); + } + + /** + * Convenience function, forwards to {@link android.util.Log#isLoggable(String, int)}. + * + * Note: Has no effect on whether logs are sent or not. Use a method with + * {@link RemovableInRelease} to log something in Debug builds only. + */ + public static boolean isLoggable(String tag, int level) { + return android.util.Log.isLoggable(tag, level); + } + + /** + * Sends a {@link android.util.Log#VERBOSE} log message. + * + * For optimization purposes, only the fixed parameters versions are visible. If you need more + * than 7 parameters, consider building your log message using a function annotated with + * {@link RemovableInRelease}. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + private static void verbose(String tag, String messageTemplate, Object... args) { + String message = formatLogWithStack(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.v(normalizeTag(tag), message, tr); + } else { + android.util.Log.v(normalizeTag(tag), message); + } + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 0 args version. */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String message) { + verbose(tag, message); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 1 arg version. */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1) { + verbose(tag, messageTemplate, arg1); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 2 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2) { + verbose(tag, messageTemplate, arg1, arg2); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 3 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v( + String tag, String messageTemplate, Object arg1, Object arg2, Object arg3) { + verbose(tag, messageTemplate, arg1, arg2, arg3); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 4 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 5 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 6 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 7 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6, Object arg7) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } + + /** + * Sends a {@link android.util.Log#DEBUG} log message. + * + * For optimization purposes, only the fixed parameters versions are visible. If you need more + * than 7 parameters, consider building your log message using a function annotated with + * {@link RemovableInRelease}. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + private static void debug(String tag, String messageTemplate, Object... args) { + String message = formatLogWithStack(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.d(normalizeTag(tag), message, tr); + } else { + android.util.Log.d(normalizeTag(tag), message); + } + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 0 args version. */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String message) { + debug(tag, message); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 1 arg version. */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1) { + debug(tag, messageTemplate, arg1); + } + /** Sends a {@link android.util.Log#DEBUG} log message. 2 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2) { + debug(tag, messageTemplate, arg1, arg2); + } + /** Sends a {@link android.util.Log#DEBUG} log message. 3 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d( + String tag, String messageTemplate, Object arg1, Object arg2, Object arg3) { + debug(tag, messageTemplate, arg1, arg2, arg3); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 4 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 5 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 6 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 7 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6, Object arg7) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } + + /** + * Sends an {@link android.util.Log#INFO} log message. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void i(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.i(normalizeTag(tag), message, tr); + } else { + android.util.Log.i(normalizeTag(tag), message); + } + } + + /** + * Sends a {@link android.util.Log#WARN} log message. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void w(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.w(normalizeTag(tag), message, tr); + } else { + android.util.Log.w(normalizeTag(tag), message); + } + } + + /** + * Sends an {@link android.util.Log#ERROR} log message. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void e(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.e(normalizeTag(tag), message, tr); + } else { + android.util.Log.e(normalizeTag(tag), message); + } + } + + /** + * What a Terrible Failure: Used for conditions that should never happen, and logged at + * the {@link android.util.Log#ASSERT} level. Depending on the configuration, it might + * terminate the process. + * + * @see android.util.Log#wtf(String, String, Throwable) + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void wtf(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.wtf(normalizeTag(tag), message, tr); + } else { + android.util.Log.wtf(normalizeTag(tag), message); + } + } + + /** Handy function to get a loggable stack trace from a Throwable. */ + public static String getStackTraceString(Throwable tr) { + return android.util.Log.getStackTraceString(tr); + } + + private static Throwable getThrowableToLog(Object[] args) { + if (args == null || args.length == 0) return null; + + Object lastArg = args[args.length - 1]; + + if (!(lastArg instanceof Throwable)) return null; + return (Throwable) lastArg; + } + + /** Returns a string form of the origin of the log call, to be used as secondary tag.*/ + private static String getCallOrigin() { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + + // The call stack should look like: + // n [a variable number of calls depending on the vm used] + // +0 getCallOrigin() + // +1 privateLogFunction: verbose or debug + // +2 formatLogWithStack() + // +3 logFunction: v or d + // +4 caller + + int callerStackIndex; + String logClassName = Log.class.getName(); + for (callerStackIndex = 0; callerStackIndex < st.length; callerStackIndex++) { + if (st[callerStackIndex].getClassName().equals(logClassName)) { + callerStackIndex += 4; + break; + } + } + + return st[callerStackIndex].getFileName() + ":" + st[callerStackIndex].getLineNumber(); + } +} diff --git a/base/android/java/src/org/chromium/base/PackageUtils.java b/base/android/java/src/org/chromium/base/PackageUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..ab554cdc45ad7c7ca1e03bcd17a1d13d756ac102 --- /dev/null +++ b/base/android/java/src/org/chromium/base/PackageUtils.java @@ -0,0 +1,37 @@ +// 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. + +package org.chromium.base; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +/** + * This class provides package checking related methods. + */ +public class PackageUtils { + /** + * Retrieves the version of the given package installed on the device. + * + * @param context Any context. + * @param packageName Name of the package to find. + * @return The package's version code if found, -1 otherwise. + */ + public static int getPackageVersion(Context context, String packageName) { + int versionCode = -1; + PackageManager pm = context.getPackageManager(); + try { + PackageInfo packageInfo = pm.getPackageInfo(packageName, 0); + if (packageInfo != null) versionCode = packageInfo.versionCode; + } catch (PackageManager.NameNotFoundException e) { + // Do nothing, versionCode stays -1 + } + return versionCode; + } + + private PackageUtils() { + // Hide constructor + } +} diff --git a/base/android/java/src/org/chromium/base/VisibleForTesting.java b/base/android/java/src/org/chromium/base/VisibleForTesting.java new file mode 100644 index 0000000000000000000000000000000000000000..24cbfadfaa0fd7816af097a9b6807d3a99229210 --- /dev/null +++ b/base/android/java/src/org/chromium/base/VisibleForTesting.java @@ -0,0 +1,12 @@ +// Copyright 2014 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. + +package org.chromium.base; + +/** + * Annotation used to mark code that has wider visibility or present for testing code. + */ +public @interface VisibleForTesting { + +} diff --git a/base/android/java/src/org/chromium/base/annotations/AccessedByNative.java b/base/android/java/src/org/chromium/base/annotations/AccessedByNative.java new file mode 100644 index 0000000000000000000000000000000000000000..6df7c11027ccf6122b0df4d6c6c8964f835b1526 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/AccessedByNative.java @@ -0,0 +1,20 @@ +// Copyright 2012 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @AccessedByNative is used to ensure proguard will keep this field, since it's + * only accessed by native. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.CLASS) +public @interface AccessedByNative { + public String value() default ""; +} diff --git a/base/android/java/src/org/chromium/base/annotations/CalledByNative.java b/base/android/java/src/org/chromium/base/annotations/CalledByNative.java new file mode 100644 index 0000000000000000000000000000000000000000..94ef3fab48a67a4ae137980eeab405700848f21d --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/CalledByNative.java @@ -0,0 +1,23 @@ +// Copyright 2012 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNative is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface CalledByNative { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +} diff --git a/base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java b/base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java new file mode 100644 index 0000000000000000000000000000000000000000..c0abcbe649901f0282b61f5a834e110d58cbe6d8 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java @@ -0,0 +1,27 @@ +// Copyright 2012 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNativeUnchecked is used to generate JNI bindings that do not check for exceptions. + * It only makes sense to use this annotation on methods that declare a throws... spec. + * However, note that the exception received native side maybe an 'unchecked' (RuntimeExpception) + * such as NullPointerException, so the native code should differentiate these cases. + * Usage of this should be very rare; where possible handle exceptions in the Java side and use a + * return value to indicate success / failure. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface CalledByNativeUnchecked { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +} diff --git a/base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java b/base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java new file mode 100644 index 0000000000000000000000000000000000000000..f1bf85ef4b13bbafe2aa5de0784bcf28e2ca7e08 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java @@ -0,0 +1,35 @@ +// Copyright 2014 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * JNIAdditionalImport is used by the JNI generator to qualify inner types used on JNI methods. Must + * be used when an inner class is used from a class within the same package. Example: + * + *
+ * @JNIAdditionImport(Foo.class)
+ * public class Bar {
+ *     @CalledByNative static void doSomethingWithInner(Foo.Inner inner) {
+ *     ...
+ *     }
+ * }
+ * 
+ * 

+ * Notes: + * 1) Foo must be in the same package as Bar + * 2) For classes in different packages, they should be imported as: + * import other.package.Foo; + * and this annotation should not be used. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface JNIAdditionalImport { + Class[] value(); +} diff --git a/base/android/java/src/org/chromium/base/annotations/JNINamespace.java b/base/android/java/src/org/chromium/base/annotations/JNINamespace.java new file mode 100644 index 0000000000000000000000000000000000000000..4cd5531fae3ad101e9f3e5322821bc76cedbc912 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/JNINamespace.java @@ -0,0 +1,20 @@ +// Copyright 2012 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @JNINamespace is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code using the specified namespace. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface JNINamespace { + public String value(); +} diff --git a/base/android/java/src/org/chromium/base/annotations/MainDex.java b/base/android/java/src/org/chromium/base/annotations/MainDex.java new file mode 100644 index 0000000000000000000000000000000000000000..0b35ade8ee62de8b33a5e75068927a1c431bb1bd --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/MainDex.java @@ -0,0 +1,21 @@ +// 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that signals that a class should be kept in the main dex file. + * + * This generally means it's used by renderer processes, which can't load secondary dexes + * on K and below. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface MainDex { +} diff --git a/base/android/java/src/org/chromium/base/annotations/NativeCall.java b/base/android/java/src/org/chromium/base/annotations/NativeCall.java new file mode 100644 index 0000000000000000000000000000000000000000..b69cd17e257e3db4af4d878e3d1d729db702e1d3 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/NativeCall.java @@ -0,0 +1,24 @@ +// Copyright 2014 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @NativeCall is used by the JNI generator to create the necessary JNI bindings + * so a native function can be bound to a Java inner class. The native class for + * which the JNI method will be generated is specified by the first parameter. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface NativeCall { + /* + * Value determines which native class the method should map to. + */ + public String value() default ""; +} diff --git a/base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java b/base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java new file mode 100644 index 0000000000000000000000000000000000000000..afbc36854e167860e0bc7e71a3e88f608ea74d2d --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java @@ -0,0 +1,25 @@ +// Copyright 2012 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @NativeClassQualifiedName is used by the JNI generator to create the necessary JNI + * bindings to call into the specified native class name. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface NativeClassQualifiedName { + /* + * Tells which native class the method is going to be bound to. + * The first parameter of the annotated method must be an int nativePtr pointing to + * an instance of this class. + */ + public String value(); +} diff --git a/base/android/java/src/org/chromium/base/annotations/RemovableInRelease.java b/base/android/java/src/org/chromium/base/annotations/RemovableInRelease.java new file mode 100644 index 0000000000000000000000000000000000000000..2191334a4dfa4184fddc4b27fdc70b80aaaa3d95 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/RemovableInRelease.java @@ -0,0 +1,18 @@ +// 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * The annotated function can be removed in release builds. + * + * Calls to this function will be removed if its return value is not used. If all calls are removed, + * the function definition itself will be candidate for removal. + * It works by indicating to Proguard that the function has no side effects. + */ +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface RemovableInRelease {} diff --git a/base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java b/base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java new file mode 100644 index 0000000000000000000000000000000000000000..89068ac9418bfb0743e472ab04bd3d0921c2aece --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java @@ -0,0 +1,20 @@ +// Copyright 2014 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @SuppressFBWarnings is used to suppress FindBugs warnings. + * + * The long name of FindBugs warnings can be found at + * http://findbugs.sourceforge.net/bugDescriptions.html + */ +@Retention(RetentionPolicy.CLASS) +public @interface SuppressFBWarnings { + String[] value() default {}; + String justification() default ""; +} diff --git a/base/android/java/src/org/chromium/base/annotations/UsedByReflection.java b/base/android/java/src/org/chromium/base/annotations/UsedByReflection.java new file mode 100644 index 0000000000000000000000000000000000000000..a2af704e0c931b2a8e4368d2fe11ced42c5d9de9 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/UsedByReflection.java @@ -0,0 +1,24 @@ +// Copyright 2014 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Annotation used for marking methods and fields that are called by reflection. + * Useful for keeping components that would otherwise be removed by Proguard. + * Use the value parameter to mention a file that calls this method. + * + * Note that adding this annotation to a method is not enough to guarantee that + * it is kept - either its class must be referenced elsewhere in the program, or + * the class must be annotated with this as well. + */ +@Target({ + ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, + ElementType.CONSTRUCTOR }) +public @interface UsedByReflection { + String value(); +} diff --git a/base/android/java_runtime.cc b/base/android/java_runtime.cc new file mode 100644 index 0000000000000000000000000000000000000000..5fae49a2adb8d6ae5b57fb5ad5bef2d14e56b516 --- /dev/null +++ b/base/android/java_runtime.cc @@ -0,0 +1,21 @@ +// 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. + +#include "base/android/java_runtime.h" + +#include "jni/Runtime_jni.h" + +namespace base { +namespace android { + +void JavaRuntime::GetMemoryUsage(long* total_memory, long* free_memory) { + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef runtime = + JNI_Runtime::Java_Runtime_getRuntime(env); + *total_memory = JNI_Runtime::Java_Runtime_totalMemory(env, runtime); + *free_memory = JNI_Runtime::Java_Runtime_freeMemory(env, runtime); +} + +} // namespace android +} // namespace base diff --git a/base/android/java_runtime.h b/base/android/java_runtime.h new file mode 100644 index 0000000000000000000000000000000000000000..2034fb9c1d071a130212bb129a72b8a50e683ad9 --- /dev/null +++ b/base/android/java_runtime.h @@ -0,0 +1,25 @@ +// 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. + +#ifndef BASE_ANDROID_JAVA_RUNTIME_H_ +#define BASE_ANDROID_JAVA_RUNTIME_H_ + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" + +namespace base { +namespace android { + +// Wrapper class for using the java.lang.Runtime object from jni. +class BASE_EXPORT JavaRuntime { + public: + // Fills the total memory used and memory allocated for objects by the java + // heap in the current process. Returns true on success. + static void GetMemoryUsage(long* total_memory, long* free_memory); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JAVA_RUNTIME_H_ diff --git a/base/android/jni_android.cc b/base/android/jni_android.cc new file mode 100644 index 0000000000000000000000000000000000000000..2b5869f06b7c412e2a13fdda9aacb355096a1ad7 --- /dev/null +++ b/base/android/jni_android.cc @@ -0,0 +1,320 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_android.h" + +#include + +#include + +#include "base/android/build_info.h" +#include "base/android/jni_string.h" +// Removed unused headers. TODO(hidehiko): Upstream. +// #include "base/android/jni_utils.h" +#include "base/debug/debugging_flags.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/thread_local.h" + +namespace { +using base::android::GetClass; +using base::android::MethodID; +using base::android::ScopedJavaLocalRef; + +base::android::JniRegistrationType g_jni_registration_type = + base::android::ALL_JNI_REGISTRATION; + +JavaVM* g_jvm = NULL; +base::LazyInstance>::Leaky + g_class_loader = LAZY_INSTANCE_INITIALIZER; +jmethodID g_class_loader_load_class_method_id = 0; + +#if BUILDFLAG(ENABLE_PROFILING) && HAVE_TRACE_STACK_FRAME_POINTERS +base::LazyInstance>::Leaky + g_stack_frame_pointer = LAZY_INSTANCE_INITIALIZER; +#endif + +} // namespace + +namespace base { +namespace android { + +JniRegistrationType GetJniRegistrationType() { + return g_jni_registration_type; +} + +void SetJniRegistrationType(JniRegistrationType jni_registration_type) { + g_jni_registration_type = jni_registration_type; +} + +JNIEnv* AttachCurrentThread() { + DCHECK(g_jvm); + JNIEnv* env = NULL; + jint ret = g_jvm->AttachCurrentThread(&env, NULL); + DCHECK_EQ(JNI_OK, ret); + return env; +} + +JNIEnv* AttachCurrentThreadWithName(const std::string& thread_name) { + DCHECK(g_jvm); + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_2; + args.name = thread_name.c_str(); + args.group = NULL; + JNIEnv* env = NULL; + jint ret = g_jvm->AttachCurrentThread(&env, &args); + DCHECK_EQ(JNI_OK, ret); + return env; +} + +void DetachFromVM() { + // Ignore the return value, if the thread is not attached, DetachCurrentThread + // will fail. But it is ok as the native thread may never be attached. + if (g_jvm) + g_jvm->DetachCurrentThread(); +} + +void InitVM(JavaVM* vm) { + DCHECK(!g_jvm || g_jvm == vm); + g_jvm = vm; +} + +bool IsVMInitialized() { + return g_jvm != NULL; +} + +void InitReplacementClassLoader(JNIEnv* env, + const JavaRef& class_loader) { + DCHECK(g_class_loader.Get().is_null()); + DCHECK(!class_loader.is_null()); + + ScopedJavaLocalRef class_loader_clazz = + GetClass(env, "java/lang/ClassLoader"); + CHECK(!ClearException(env)); + g_class_loader_load_class_method_id = + env->GetMethodID(class_loader_clazz.obj(), + "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + CHECK(!ClearException(env)); + + DCHECK(env->IsInstanceOf(class_loader.obj(), class_loader_clazz.obj())); + g_class_loader.Get().Reset(class_loader); +} + +ScopedJavaLocalRef GetClass(JNIEnv* env, const char* class_name) { + jclass clazz; + if (!g_class_loader.Get().is_null()) { + // ClassLoader.loadClass expects a classname with components separated by + // dots instead of the slashes that JNIEnv::FindClass expects. The JNI + // generator generates names with slashes, so we have to replace them here. + // TODO(torne): move to an approach where we always use ClassLoader except + // for the special case of base::android::GetClassLoader(), and change the + // JNI generator to generate dot-separated names. http://crbug.com/461773 + size_t bufsize = strlen(class_name) + 1; + char dotted_name[bufsize]; + memmove(dotted_name, class_name, bufsize); + for (size_t i = 0; i < bufsize; ++i) { + if (dotted_name[i] == '/') { + dotted_name[i] = '.'; + } + } + + clazz = static_cast( + env->CallObjectMethod(g_class_loader.Get().obj(), + g_class_loader_load_class_method_id, + ConvertUTF8ToJavaString(env, dotted_name).obj())); + } else { + clazz = env->FindClass(class_name); + } + if (ClearException(env) || !clazz) { + LOG(FATAL) << "Failed to find class " << class_name; + } + return ScopedJavaLocalRef(env, clazz); +} + +jclass LazyGetClass( + JNIEnv* env, + const char* class_name, + base::subtle::AtomicWord* atomic_class_id) { + static_assert(sizeof(subtle::AtomicWord) >= sizeof(jclass), + "AtomicWord can't be smaller than jclass"); + subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_class_id); + if (value) + return reinterpret_cast(value); + ScopedJavaGlobalRef clazz; + clazz.Reset(GetClass(env, class_name)); + subtle::AtomicWord null_aw = reinterpret_cast(NULL); + subtle::AtomicWord cas_result = base::subtle::Release_CompareAndSwap( + atomic_class_id, + null_aw, + reinterpret_cast(clazz.obj())); + if (cas_result == null_aw) { + // We intentionally leak the global ref since we now storing it as a raw + // pointer in |atomic_class_id|. + return clazz.Release(); + } else { + return reinterpret_cast(cas_result); + } +} + +template +jmethodID MethodID::Get(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature) { + jmethodID id = type == TYPE_STATIC ? + env->GetStaticMethodID(clazz, method_name, jni_signature) : + env->GetMethodID(clazz, method_name, jni_signature); + if (base::android::ClearException(env) || !id) { + LOG(FATAL) << "Failed to find " << + (type == TYPE_STATIC ? "static " : "") << + "method " << method_name << " " << jni_signature; + } + return id; +} + +// If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call +// into ::Get() above. If there's a race, it's ok since the values are the same +// (and the duplicated effort will happen only once). +template +jmethodID MethodID::LazyGet(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature, + base::subtle::AtomicWord* atomic_method_id) { + static_assert(sizeof(subtle::AtomicWord) >= sizeof(jmethodID), + "AtomicWord can't be smaller than jMethodID"); + subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_method_id); + if (value) + return reinterpret_cast(value); + jmethodID id = MethodID::Get(env, clazz, method_name, jni_signature); + base::subtle::Release_Store( + atomic_method_id, reinterpret_cast(id)); + return id; +} + +// Various template instantiations. +template jmethodID MethodID::Get( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::Get( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::LazyGet( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); + +template jmethodID MethodID::LazyGet( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); + +bool HasException(JNIEnv* env) { + return env->ExceptionCheck() != JNI_FALSE; +} + +bool ClearException(JNIEnv* env) { + if (!HasException(env)) + return false; + env->ExceptionDescribe(); + env->ExceptionClear(); + return true; +} + +void CheckException(JNIEnv* env) { + if (!HasException(env)) + return; + + // Exception has been found, might as well tell breakpad about it. + jthrowable java_throwable = env->ExceptionOccurred(); + if (java_throwable) { + // Clear the pending exception, since a local reference is now held. + env->ExceptionDescribe(); + env->ExceptionClear(); + + // Set the exception_string in BuildInfo so that breakpad can read it. + // RVO should avoid any extra copies of the exception string. + base::android::BuildInfo::GetInstance()->SetJavaExceptionInfo( + GetJavaExceptionInfo(env, java_throwable)); + } + + // Now, feel good about it and die. + // TODO(lhchavez): Remove this hack. See b/28814913 for details. + // We're using BuildInfo's java_exception_info() instead of storing the + // exception info a few lines above to avoid extra copies. It will be + // truncated to 1024 bytes anyways. + const char* exception_string = + base::android::BuildInfo::GetInstance()->java_exception_info(); + if (exception_string) + LOG(FATAL) << exception_string; + else + LOG(FATAL) << "Unhandled exception"; +} + +std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { + ScopedJavaLocalRef throwable_clazz = + GetClass(env, "java/lang/Throwable"); + jmethodID throwable_printstacktrace = + MethodID::Get( + env, throwable_clazz.obj(), "printStackTrace", + "(Ljava/io/PrintStream;)V"); + + // Create an instance of ByteArrayOutputStream. + ScopedJavaLocalRef bytearray_output_stream_clazz = + GetClass(env, "java/io/ByteArrayOutputStream"); + jmethodID bytearray_output_stream_constructor = + MethodID::Get( + env, bytearray_output_stream_clazz.obj(), "", "()V"); + jmethodID bytearray_output_stream_tostring = + MethodID::Get( + env, bytearray_output_stream_clazz.obj(), "toString", + "()Ljava/lang/String;"); + ScopedJavaLocalRef bytearray_output_stream(env, + env->NewObject(bytearray_output_stream_clazz.obj(), + bytearray_output_stream_constructor)); + + // Create an instance of PrintStream. + ScopedJavaLocalRef printstream_clazz = + GetClass(env, "java/io/PrintStream"); + jmethodID printstream_constructor = + MethodID::Get( + env, printstream_clazz.obj(), "", + "(Ljava/io/OutputStream;)V"); + ScopedJavaLocalRef printstream(env, + env->NewObject(printstream_clazz.obj(), printstream_constructor, + bytearray_output_stream.obj())); + + // Call Throwable.printStackTrace(PrintStream) + env->CallVoidMethod(java_throwable, throwable_printstacktrace, + printstream.obj()); + + // Call ByteArrayOutputStream.toString() + ScopedJavaLocalRef exception_string( + env, static_cast( + env->CallObjectMethod(bytearray_output_stream.obj(), + bytearray_output_stream_tostring))); + + return ConvertJavaStringToUTF8(exception_string); +} + +#if BUILDFLAG(ENABLE_PROFILING) && HAVE_TRACE_STACK_FRAME_POINTERS + +JNIStackFrameSaver::JNIStackFrameSaver(void* current_fp) { + previous_fp_ = g_stack_frame_pointer.Pointer()->Get(); + g_stack_frame_pointer.Pointer()->Set(current_fp); +} + +JNIStackFrameSaver::~JNIStackFrameSaver() { + g_stack_frame_pointer.Pointer()->Set(previous_fp_); +} + +void* JNIStackFrameSaver::SavedFrame() { + return g_stack_frame_pointer.Pointer()->Get(); +} + +#endif // ENABLE_PROFILING && HAVE_TRACE_STACK_FRAME_POINTERS + +} // namespace android +} // namespace base diff --git a/base/android/jni_android.h b/base/android/jni_android.h new file mode 100644 index 0000000000000000000000000000000000000000..de53c10f6d43e82099065427db083ff2547cf784 --- /dev/null +++ b/base/android/jni_android.h @@ -0,0 +1,190 @@ +// Copyright (c) 2012 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 BASE_ANDROID_JNI_ANDROID_H_ +#define BASE_ANDROID_JNI_ANDROID_H_ + +#include +#include + +#include + +#include "base/android/scoped_java_ref.h" +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/debug/stack_trace.h" +#include "base/macros.h" + +#if HAVE_TRACE_STACK_FRAME_POINTERS + +// When profiling is enabled (enable_profiling=true) this macro is added to +// all generated JNI stubs so that it becomes the last thing that runs before +// control goes into Java. +// +// This macro saves stack frame pointer of the current function. Saved value +// used later by JNI_LINK_SAVED_FRAME_POINTER. +#define JNI_SAVE_FRAME_POINTER \ + base::android::JNIStackFrameSaver jni_frame_saver(__builtin_frame_address(0)) + +// When profiling is enabled (enable_profiling=true) this macro is added to +// all generated JNI callbacks so that it becomes the first thing that runs +// after control returns from Java. +// +// This macro links stack frame of the current function to the stack frame +// saved by JNI_SAVE_FRAME_POINTER, allowing frame-based unwinding +// (used by the heap profiler) to produce complete traces. +#define JNI_LINK_SAVED_FRAME_POINTER \ + base::debug::ScopedStackFrameLinker jni_frame_linker( \ + __builtin_frame_address(0), \ + base::android::JNIStackFrameSaver::SavedFrame()) + +#else + +// Frame-based stack unwinding is not supported, do nothing. +#define JNI_SAVE_FRAME_POINTER +#define JNI_LINK_SAVED_FRAME_POINTER + +#endif // HAVE_TRACE_STACK_FRAME_POINTERS + +namespace base { +namespace android { + +// Used to mark symbols to be exported in a shared library's symbol table. +#define JNI_EXPORT __attribute__ ((visibility("default"))) + +// The level of JNI registration required for the current process. +enum JniRegistrationType { + // Register all native methods. + ALL_JNI_REGISTRATION, + // Register some native methods, as controlled by the jni_generator. + SELECTIVE_JNI_REGISTRATION, + // Do not register any native methods. + NO_JNI_REGISTRATION, +}; + +BASE_EXPORT JniRegistrationType GetJniRegistrationType(); + +// Set the JniRegistrationType for this process (defaults to +// ALL_JNI_REGISTRATION). This should be called in the JNI_OnLoad function +// which is called when the native library is first loaded. +BASE_EXPORT void SetJniRegistrationType( + JniRegistrationType jni_registration_type); + +// Contains the registration method information for initializing JNI bindings. +struct RegistrationMethod { + const char* name; + bool (*func)(JNIEnv* env); +}; + +// Attaches the current thread to the VM (if necessary) and return the JNIEnv*. +BASE_EXPORT JNIEnv* AttachCurrentThread(); + +// Same to AttachCurrentThread except that thread name will be set to +// |thread_name| if it is the first call. Otherwise, thread_name won't be +// changed. AttachCurrentThread() doesn't regard underlying platform thread +// name, but just resets it to "Thread-???". This function should be called +// right after new thread is created if it is important to keep thread name. +BASE_EXPORT JNIEnv* AttachCurrentThreadWithName(const std::string& thread_name); + +// Detaches the current thread from VM if it is attached. +BASE_EXPORT void DetachFromVM(); + +// Initializes the global JVM. +BASE_EXPORT void InitVM(JavaVM* vm); + +// Returns true if the global JVM has been initialized. +BASE_EXPORT bool IsVMInitialized(); + +// Initializes the global ClassLoader used by the GetClass and LazyGetClass +// methods. This is needed because JNI will use the base ClassLoader when there +// is no Java code on the stack. The base ClassLoader doesn't know about any of +// the application classes and will fail to lookup anything other than system +// classes. +BASE_EXPORT void InitReplacementClassLoader( + JNIEnv* env, + const JavaRef& class_loader); + +// Finds the class named |class_name| and returns it. +// Use this method instead of invoking directly the JNI FindClass method (to +// prevent leaking local references). +// This method triggers a fatal assertion if the class could not be found. +// Use HasClass if you need to check whether the class exists. +BASE_EXPORT ScopedJavaLocalRef GetClass(JNIEnv* env, + const char* class_name); + +// The method will initialize |atomic_class_id| to contain a global ref to the +// class. And will return that ref on subsequent calls. It's the caller's +// responsibility to release the ref when it is no longer needed. +// The caller is responsible to zero-initialize |atomic_method_id|. +// It's fine to simultaneously call this on multiple threads referencing the +// same |atomic_method_id|. +BASE_EXPORT jclass LazyGetClass( + JNIEnv* env, + const char* class_name, + base::subtle::AtomicWord* atomic_class_id); + +// This class is a wrapper for JNIEnv Get(Static)MethodID. +class BASE_EXPORT MethodID { + public: + enum Type { + TYPE_STATIC, + TYPE_INSTANCE, + }; + + // Returns the method ID for the method with the specified name and signature. + // This method triggers a fatal assertion if the method could not be found. + template + static jmethodID Get(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature); + + // The caller is responsible to zero-initialize |atomic_method_id|. + // It's fine to simultaneously call this on multiple threads referencing the + // same |atomic_method_id|. + template + static jmethodID LazyGet(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature, + base::subtle::AtomicWord* atomic_method_id); +}; + +// Returns true if an exception is pending in the provided JNIEnv*. +BASE_EXPORT bool HasException(JNIEnv* env); + +// If an exception is pending in the provided JNIEnv*, this function clears it +// and returns true. +BASE_EXPORT bool ClearException(JNIEnv* env); + +// This function will call CHECK() macro if there's any pending exception. +BASE_EXPORT void CheckException(JNIEnv* env); + +// This returns a string representation of the java stack trace. +BASE_EXPORT std::string GetJavaExceptionInfo(JNIEnv* env, + jthrowable java_throwable); + +#if HAVE_TRACE_STACK_FRAME_POINTERS + +// Saves caller's PC and stack frame in a thread-local variable. +// Implemented only when profiling is enabled (enable_profiling=true). +class BASE_EXPORT JNIStackFrameSaver { + public: + JNIStackFrameSaver(void* current_fp); + ~JNIStackFrameSaver(); + static void* SavedFrame(); + + private: + void* previous_fp_; + + DISALLOW_COPY_AND_ASSIGN(JNIStackFrameSaver); +}; + +#endif // HAVE_TRACE_STACK_FRAME_POINTERS + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_ANDROID_H_ diff --git a/base/android/jni_android_unittest.cc b/base/android/jni_android_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..dabd480072c5b28e504081b0d41dfbd70c072805 --- /dev/null +++ b/base/android/jni_android_unittest.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_android.h" + +#include "base/at_exit.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { + +base::subtle::AtomicWord g_atomic_id = 0; +int LazyMethodIDCall(JNIEnv* env, jclass clazz, int p) { + jmethodID id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, clazz, + "abs", + "(I)I", + &g_atomic_id); + + return env->CallStaticIntMethod(clazz, id, p); +} + +int MethodIDCall(JNIEnv* env, jclass clazz, jmethodID id, int p) { + return env->CallStaticIntMethod(clazz, id, p); +} + +} // namespace + +TEST(JNIAndroidMicrobenchmark, MethodId) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef clazz(GetClass(env, "java/lang/Math")); + base::Time start_lazy = base::Time::Now(); + int o = 0; + for (int i = 0; i < 1024; ++i) + o += LazyMethodIDCall(env, clazz.obj(), i); + base::Time end_lazy = base::Time::Now(); + + jmethodID id = reinterpret_cast(g_atomic_id); + base::Time start = base::Time::Now(); + for (int i = 0; i < 1024; ++i) + o += MethodIDCall(env, clazz.obj(), id, i); + base::Time end = base::Time::Now(); + + // On a Galaxy Nexus, results were in the range of: + // JNI LazyMethodIDCall (us) 1984 + // JNI MethodIDCall (us) 1861 + LOG(ERROR) << "JNI LazyMethodIDCall (us) " << + base::TimeDelta(end_lazy - start_lazy).InMicroseconds(); + LOG(ERROR) << "JNI MethodIDCall (us) " << + base::TimeDelta(end - start).InMicroseconds(); + LOG(ERROR) << "JNI " << o; +} + + +} // namespace android +} // namespace base diff --git a/base/android/jni_generator/android_jar.classes b/base/android/jni_generator/android_jar.classes new file mode 100644 index 0000000000000000000000000000000000000000..7d412cee471d47e157b349d5264d08546292d65f --- /dev/null +++ b/base/android/jni_generator/android_jar.classes @@ -0,0 +1,98 @@ +java/lang/AbstractMethodError.class +java/lang/AbstractStringBuilder.class +java/lang/Appendable.class +java/lang/ArithmeticException.class +java/lang/ArrayIndexOutOfBoundsException.class +java/lang/ArrayStoreException.class +java/lang/AssertionError.class +java/lang/AutoCloseable.class +java/lang/Boolean.class +java/lang/Byte.class +java/lang/Character.class +java/lang/Character$Subset.class +java/lang/Character$UnicodeBlock.class +java/lang/CharSequence.class +java/lang/ClassCastException.class +java/lang/ClassCircularityError.class +java/lang/Class.class +java/lang/ClassFormatError.class +java/lang/ClassLoader.class +java/lang/ClassNotFoundException.class +java/lang/Cloneable.class +java/lang/CloneNotSupportedException.class +java/lang/Comparable.class +java/lang/Compiler.class +java/lang/Deprecated.class +java/lang/Double.class +java/lang/Enum.class +java/lang/EnumConstantNotPresentException.class +java/lang/Error.class +java/lang/Exception.class +java/lang/ExceptionInInitializerError.class +java/lang/Float.class +java/lang/IllegalAccessError.class +java/lang/IllegalAccessException.class +java/lang/IllegalArgumentException.class +java/lang/IllegalMonitorStateException.class +java/lang/IllegalStateException.class +java/lang/IncompatibleClassChangeError.class +java/lang/IndexOutOfBoundsException.class +java/lang/InheritableThreadLocal.class +java/lang/InstantiationError.class +java/lang/InstantiationException.class +java/lang/Integer.class +java/lang/InternalError.class +java/lang/InterruptedException.class +java/lang/Iterable.class +java/lang/LinkageError.class +java/lang/Long.class +java/lang/Math.class +java/lang/NegativeArraySizeException.class +java/lang/NoClassDefFoundError.class +java/lang/NoSuchFieldError.class +java/lang/NoSuchFieldException.class +java/lang/NoSuchMethodError.class +java/lang/NoSuchMethodException.class +java/lang/NullPointerException.class +java/lang/Number.class +java/lang/NumberFormatException.class +java/lang/Object.class +java/lang/OutOfMemoryError.class +java/lang/Override.class +java/lang/Package.class +java/lang/ProcessBuilder.class +java/lang/Process.class +java/lang/Readable.class +java/lang/ReflectiveOperationException.class +java/lang/Runnable.class +java/lang/Runtime.class +java/lang/RuntimeException.class +java/lang/RuntimePermission.class +java/lang/SafeVarargs.class +java/lang/SecurityException.class +java/lang/SecurityManager.class +java/lang/Short.class +java/lang/StackOverflowError.class +java/lang/StackTraceElement.class +java/lang/StrictMath.class +java/lang/StringBuffer.class +java/lang/StringBuilder.class +java/lang/String.class +java/lang/StringIndexOutOfBoundsException.class +java/lang/SuppressWarnings.class +java/lang/System.class +java/lang/Thread.class +java/lang/ThreadDeath.class +java/lang/ThreadGroup.class +java/lang/ThreadLocal.class +java/lang/Thread$State.class +java/lang/Thread$UncaughtExceptionHandler.class +java/lang/Throwable.class +java/lang/TypeNotPresentException.class +java/lang/UnknownError.class +java/lang/UnsatisfiedLinkError.class +java/lang/UnsupportedClassVersionError.class +java/lang/UnsupportedOperationException.class +java/lang/VerifyError.class +java/lang/VirtualMachineError.class +java/lang/Void.class diff --git a/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java b/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java new file mode 100644 index 0000000000000000000000000000000000000000..42d8e56bbf006b0e10c9bccf85b633884834aca6 --- /dev/null +++ b/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java @@ -0,0 +1,318 @@ +// Copyright 2012 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. + +package org.chromium.example.jni_generator; + +import android.graphics.Rect; + +import org.chromium.base.annotations.AccessedByNative; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.CalledByNativeUnchecked; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeCall; +import org.chromium.base.annotations.NativeClassQualifiedName; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +// This class serves as a reference test for the bindings generator, and as example documentation +// for how to use the jni generator. +// The C++ counter-part is sample_for_tests.cc. +// jni_generator/BUILD.gn has a jni_generator_tests target that will: +// * Generate a header file for the JNI bindings based on this file. +// * Compile sample_for_tests.cc using the generated header file. +// * link a native executable to prove the generated header + cc file are self-contained. +// All comments are informational only, and are ignored by the jni generator. +// +// Binding C/C++ with Java is not trivial, specially when ownership and object lifetime +// semantics needs to be managed across boundaries. +// Following a few guidelines will make the code simpler and less buggy: +// +// - Never write any JNI "by hand". Rely on the bindings generator to have a thin +// layer of type-safety. +// +// - Treat the types from the other side as "opaque" as possible. Do not inspect any +// object directly, but rather, rely on well-defined getters / setters. +// +// - Minimize the surface API between the two sides, and rather than calling multiple +// functions across boundaries, call only one (and then, internally in the other side, +// call as many little functions as required). +// +// - If a Java object "owns" a native object, stash the pointer in a "long mNativeClassName". +// Note that it needs to have a "destruction path", i.e., it must eventually call a method +// to delete the native object (for example, the java object has a "close()" method that +// in turn deletes the native object). Avoid relying on finalizers: those run in a different +// thread and makes the native lifetime management more difficult. +// +// - For native object "owning" java objects: +// - If there's a strong 1:1 to relationship between native and java, the best way is to +// stash the java object into a base::android::ScopedJavaGlobalRef. This will ensure the +// java object can be GC'd once the native object is destroyed but note that this global strong +// ref implies a new GC root, so be sure it will not leak and it must never rely on being +// triggered (transitively) from a java side GC. +// - In all other cases, the native side should keep a JavaObjectWeakGlobalRef, and check whether +// that reference is still valid before de-referencing it. Note that you will need another +// java-side object to be holding a strong reference to this java object while it is in use, to +// avoid unpredictable GC of the object before native side has finished with it. +// +// - The best way to pass "compound" datatypes across in either direction is to create an inner +// class with PODs and a factory function. If possible, make it immutable (i.e., mark all the +// fields as "final"). See examples with "InnerStructB" below. +// +// - It's simpler to create thin wrappers with a well defined JNI interface than to +// expose a lot of internal details. This is specially significant for system classes where it's +// simpler to wrap factory methods and a few getters / setters than expose the entire class. +// +// - Use static factory functions annotated with @CalledByNative rather than calling the +// constructors directly. +// +// - Iterate over containers where they are originally owned, then create inner structs or +// directly call methods on the other side. It's much simpler than trying to amalgamate +// java and stl containers. +// +// An important note about qualified class name resolution: +// The generator doesn't compile the class and have little context about the +// classes being passed through the JNI layers. It adds a few simple rules: +// +// - all classes are either explicitly imported, or they are assumed to be in +// the same package. +// +// - Inner class needs to be done through an import and usage of the +// outer class, so that the generator knows how to qualify it: +// import foo.bar.Zoo; +// void call(Zoo.Inner); +// +// - implicitly imported classes aren't supported, so in order to pass +// things like Runnable, please import java.lang.Runnable; +// +// This JNINamespace annotation indicates that all native methods should be +// generated inside this namespace, including the native class that this +// object binds to. +@JNINamespace("base::android") +class SampleForTests { + // Classes can store their C++ pointer counter part as an int that is normally initialized by + // calling out a nativeInit() function. Replace "CPPClass" with your particular class name! + long mNativeCPPObject; + + // You can define methods and attributes on the java class just like any other. + // Methods without the @CalledByNative annotation won't be exposed to JNI. + public SampleForTests() { + } + + public void startExample() { + // Calls C++ Init(...) method and holds a pointer to the C++ class. + mNativeCPPObject = nativeInit("myParam"); + } + + public void doStuff() { + // This will call CPPClass::Method() using nativePtr as a pointer to the object. This must + // be done to: + // * avoid leaks. + // * using finalizers are not allowed to destroy the cpp class. + nativeMethod(mNativeCPPObject); + } + + public void finishExample() { + // We're done, so let's destroy nativePtr object. + nativeDestroy(mNativeCPPObject); + } + + // --------------------------------------------------------------------------------------------- + // The following methods demonstrate exporting Java methods for invocation from C++ code. + // Java functions are mapping into C global functions by prefixing the method name with + // "Java__" + // This is triggered by the @CalledByNative annotation; the methods may be named as you wish. + + // Exported to C++ as: + // Java_SampleForTests_javaMethod(JNIEnv* env, jobject caller, jint foo, jint bar) + // Typically the C++ code would have obtained the jobject via the Init() call described above. + @CalledByNative + public int javaMethod(int foo, int bar) { + return 0; + } + + // Exported to C++ as Java_SampleForTests_staticJavaMethod(JNIEnv* env) + // Note no jobject argument, as it is static. + @CalledByNative + public static boolean staticJavaMethod() { + return true; + } + + // No prefix, so this method is package private. It will still be exported. + @CalledByNative + void packagePrivateJavaMethod() { + } + + // Note the "Unchecked" suffix. By default, @CalledByNative will always generate bindings that + // call CheckException(). With "@CalledByNativeUnchecked", the client C++ code is responsible to + // call ClearException() and act as appropriate. + // See more details at the "@CalledByNativeUnchecked" annotation. + @CalledByNativeUnchecked + void methodThatThrowsException() throws Exception {} + + // The generator is not confused by inline comments: + // @CalledByNative void thisShouldNotAppearInTheOutput(); + // @CalledByNativeUnchecked public static void neitherShouldThis(int foo); + + /** + * The generator is not confused by block comments: + * @CalledByNative void thisShouldNotAppearInTheOutputEither(); + * @CalledByNativeUnchecked public static void andDefinitelyNotThis(int foo); + */ + + // String constants that look like comments don't confuse the generator: + private String mArrgh = "*/*"; + + private @interface SomeAnnotation {} + + // The generator is not confused by @Annotated parameters. + @CalledByNative + void javaMethodWithAnnotatedParam(@SomeAnnotation int foo) { + } + + // --------------------------------------------------------------------------------------------- + // Java fields which are accessed from C++ code only must be annotated with @AccessedByNative to + // prevent them being eliminated when unreferenced code is stripped. + @AccessedByNative + private int mJavaField; + + // --------------------------------------------------------------------------------------------- + // The following methods demonstrate declaring methods to call into C++ from Java. + // The generator detects the "native" and "static" keywords, the type and name of the first + // parameter, and the "native" prefix to the function name to determine the C++ function + // signatures. Besides these constraints the methods can be freely named. + + // This declares a C++ function which the application code must implement: + // static jint Init(JNIEnv* env, jobject caller); + // The jobject parameter refers back to this java side object instance. + // The implementation must return the pointer to the C++ object cast to jint. + // The caller of this method should store it, and supply it as a the nativeCPPClass param to + // subsequent native method calls (see the methods below that take an "int native..." as first + // param). + private native long nativeInit(String param); + + // This defines a function binding to the associated C++ class member function. The name is + // derived from |nativeDestroy| and |nativeCPPClass| to arrive at CPPClass::Destroy() (i.e. + // native prefixes stripped). + // + // The |nativeCPPClass| is automatically cast to type CPPClass*, in order to obtain the object + // on + // which to invoke the member function. Replace "CPPClass" with your particular class name! + private native void nativeDestroy(long nativeCPPClass); + + // This declares a C++ function which the application code must implement: + // static jdouble GetDoubleFunction(JNIEnv* env, jobject caller); + // The jobject parameter refers back to this java side object instance. + private native double nativeGetDoubleFunction(); + + // Similar to nativeGetDoubleFunction(), but here the C++ side will receive a jclass rather than + // jobject param, as the function is declared static. + private static native float nativeGetFloatFunction(); + + // This function takes a non-POD datatype. We have a list mapping them to their full classpath + // in jni_generator.py JavaParamToJni. If you require a new datatype, make sure you add to that + // function. + private native void nativeSetNonPODDatatype(Rect rect); + + // This declares a C++ function which the application code must implement: + // static ScopedJavaLocalRef GetNonPODDatatype(JNIEnv* env, jobject caller); + // The jobject parameter refers back to this java side object instance. + // Note that it returns a ScopedJavaLocalRef so that you don' have to worry about + // deleting the JNI local reference. This is similar with Strings and arrays. + private native Object nativeGetNonPODDatatype(); + + // Similar to nativeDestroy above, this will cast nativeCPPClass into pointer of CPPClass type + // and call its Method member function. Replace "CPPClass" with your particular class name! + private native int nativeMethod(long nativeCPPClass); + + // Similar to nativeMethod above, but here the C++ fully qualified class name is taken from the + // annotation rather than parameter name, which can thus be chosen freely. + @NativeClassQualifiedName("CPPClass::InnerClass") + private native double nativeMethodOtherP0(long nativePtr); + + // This "struct" will be created by the native side using |createInnerStructA|, + // and used by the java-side somehow. + // Note that |@CalledByNative| has to contain the inner class name. + static class InnerStructA { + private final long mLong; + private final int mInt; + private final String mString; + + private InnerStructA(long l, int i, String s) { + mLong = l; + mInt = i; + mString = s; + } + + @CalledByNative("InnerStructA") + private static InnerStructA create(long l, int i, String s) { + return new InnerStructA(l, i, s); + } + } + + private List mListInnerStructA = new ArrayList(); + + @CalledByNative + private void addStructA(InnerStructA a) { + // Called by the native side to append another element. + mListInnerStructA.add(a); + } + + @CalledByNative + private void iterateAndDoSomething() { + Iterator it = mListInnerStructA.iterator(); + while (it.hasNext()) { + InnerStructA element = it.next(); + // Now, do something with element. + } + // Done, clear the list. + mListInnerStructA.clear(); + } + + // This "struct" will be created by the java side passed to native, which + // will use its getters. + // Note that |@CalledByNative| has to contain the inner class name. + static class InnerStructB { + private final long mKey; + private final String mValue; + + private InnerStructB(long k, String v) { + mKey = k; + mValue = v; + } + + @CalledByNative("InnerStructB") + private long getKey() { + return mKey; + } + + @CalledByNative("InnerStructB") + private String getValue() { + return mValue; + } + } + + List mListInnerStructB = new ArrayList(); + + void iterateAndDoSomethingWithMap() { + Iterator it = mListInnerStructB.iterator(); + while (it.hasNext()) { + InnerStructB element = it.next(); + // Now, do something with element. + nativeAddStructB(mNativeCPPObject, element); + } + nativeIterateAndDoSomethingWithStructB(mNativeCPPObject); + } + + native void nativeAddStructB(long nativeCPPClass, InnerStructB b); + native void nativeIterateAndDoSomethingWithStructB(long nativeCPPClass); + native String nativeReturnAString(long nativeCPPClass); + + // This inner class shows how to annotate native methods on inner classes. + static class InnerClass { + @NativeCall("InnerClass") + private static native int nativeGetInnerIntFunction(); + } +} diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py new file mode 100755 index 0000000000000000000000000000000000000000..99d8b424f02db8a4753ba96c29cc83c66d26de9b --- /dev/null +++ b/base/android/jni_generator/jni_generator.py @@ -0,0 +1,1418 @@ +#!/usr/bin/env python +# Copyright (c) 2012 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. + +"""Extracts native methods from a Java file and generates the JNI bindings. +If you change this, please run and update the tests.""" + +import collections +import errno +import optparse +import os +import re +import string +from string import Template +import subprocess +import sys +import textwrap +import zipfile + +CHROMIUM_SRC = os.path.join( + os.path.dirname(__file__), os.pardir, os.pardir, os.pardir) +BUILD_ANDROID_GYP = os.path.join( + CHROMIUM_SRC, 'build', 'android', 'gyp') + +sys.path.append(BUILD_ANDROID_GYP) + +from util import build_utils + + +class ParseError(Exception): + """Exception thrown when we can't parse the input file.""" + + def __init__(self, description, *context_lines): + Exception.__init__(self) + self.description = description + self.context_lines = context_lines + + def __str__(self): + context = '\n'.join(self.context_lines) + return '***\nERROR: %s\n\n%s\n***' % (self.description, context) + + +class Param(object): + """Describes a param for a method, either java or native.""" + + def __init__(self, **kwargs): + self.datatype = kwargs['datatype'] + self.name = kwargs['name'] + + +class NativeMethod(object): + """Describes a C/C++ method that is called by Java code""" + + def __init__(self, **kwargs): + self.static = kwargs['static'] + self.java_class_name = kwargs['java_class_name'] + self.return_type = kwargs['return_type'] + self.name = kwargs['name'] + self.params = kwargs['params'] + if self.params: + assert type(self.params) is list + assert type(self.params[0]) is Param + if (self.params and + self.params[0].datatype == kwargs.get('ptr_type', 'int') and + self.params[0].name.startswith('native')): + self.type = 'method' + self.p0_type = self.params[0].name[len('native'):] + if kwargs.get('native_class_name'): + self.p0_type = kwargs['native_class_name'] + else: + self.type = 'function' + self.method_id_var_name = kwargs.get('method_id_var_name', None) + + +class CalledByNative(object): + """Describes a java method exported to c/c++""" + + def __init__(self, **kwargs): + self.system_class = kwargs['system_class'] + self.unchecked = kwargs['unchecked'] + self.static = kwargs['static'] + self.java_class_name = kwargs['java_class_name'] + self.return_type = kwargs['return_type'] + self.name = kwargs['name'] + self.params = kwargs['params'] + self.method_id_var_name = kwargs.get('method_id_var_name', None) + self.signature = kwargs.get('signature') + self.is_constructor = kwargs.get('is_constructor', False) + self.env_call = GetEnvCall(self.is_constructor, self.static, + self.return_type) + self.static_cast = GetStaticCastForReturnType(self.return_type) + + +class ConstantField(object): + def __init__(self, **kwargs): + self.name = kwargs['name'] + self.value = kwargs['value'] + + +def JavaDataTypeToC(java_type): + """Returns a C datatype for the given java type.""" + java_pod_type_map = { + 'int': 'jint', + 'byte': 'jbyte', + 'char': 'jchar', + 'short': 'jshort', + 'boolean': 'jboolean', + 'long': 'jlong', + 'double': 'jdouble', + 'float': 'jfloat', + } + java_type_map = { + 'void': 'void', + 'String': 'jstring', + 'Throwable': 'jthrowable', + 'java/lang/String': 'jstring', + 'java/lang/Class': 'jclass', + 'java/lang/Throwable': 'jthrowable', + } + + if java_type in java_pod_type_map: + return java_pod_type_map[java_type] + elif java_type in java_type_map: + return java_type_map[java_type] + elif java_type.endswith('[]'): + if java_type[:-2] in java_pod_type_map: + return java_pod_type_map[java_type[:-2]] + 'Array' + return 'jobjectArray' + elif java_type.startswith('Class'): + # Checking just the start of the name, rather than a direct comparison, + # in order to handle generics. + return 'jclass' + else: + return 'jobject' + + +def WrapCTypeForDeclaration(c_type): + """Wrap the C datatype in a JavaRef if required.""" + if re.match(RE_SCOPED_JNI_TYPES, c_type): + return 'const base::android::JavaParamRef<' + c_type + '>&' + else: + return c_type + + +def JavaDataTypeToCForDeclaration(java_type): + """Returns a JavaRef-wrapped C datatype for the given java type.""" + return WrapCTypeForDeclaration(JavaDataTypeToC(java_type)) + + +def JavaDataTypeToCForCalledByNativeParam(java_type): + """Returns a C datatype to be when calling from native.""" + if java_type == 'int': + return 'JniIntWrapper' + else: + c_type = JavaDataTypeToC(java_type) + if re.match(RE_SCOPED_JNI_TYPES, c_type): + return 'const base::android::JavaRefOrBare<' + c_type + '>&' + else: + return c_type + + +def JavaReturnValueToC(java_type): + """Returns a valid C return value for the given java type.""" + java_pod_type_map = { + 'int': '0', + 'byte': '0', + 'char': '0', + 'short': '0', + 'boolean': 'false', + 'long': '0', + 'double': '0', + 'float': '0', + 'void': '' + } + return java_pod_type_map.get(java_type, 'NULL') + + +class JniParams(object): + _imports = [] + _fully_qualified_class = '' + _package = '' + _inner_classes = [] + _implicit_imports = [] + + @staticmethod + def SetFullyQualifiedClass(fully_qualified_class): + JniParams._fully_qualified_class = 'L' + fully_qualified_class + JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1]) + + @staticmethod + def AddAdditionalImport(class_name): + assert class_name.endswith('.class') + raw_class_name = class_name[:-len('.class')] + if '.' in raw_class_name: + raise SyntaxError('%s cannot be used in @JNIAdditionalImport. ' + 'Only import unqualified outer classes.' % class_name) + new_import = 'L%s/%s' % (JniParams._package, raw_class_name) + if new_import in JniParams._imports: + raise SyntaxError('Do not use JNIAdditionalImport on an already ' + 'imported class: %s' % (new_import.replace('/', '.'))) + JniParams._imports += [new_import] + + @staticmethod + def ExtractImportsAndInnerClasses(contents): + if not JniParams._package: + raise RuntimeError('SetFullyQualifiedClass must be called before ' + 'ExtractImportsAndInnerClasses') + contents = contents.replace('\n', '') + re_import = re.compile(r'import.*?(?P\S*?);') + for match in re.finditer(re_import, contents): + JniParams._imports += ['L' + match.group('class').replace('.', '/')] + + re_inner = re.compile(r'(class|interface)\s+?(?P\w+?)\W') + for match in re.finditer(re_inner, contents): + inner = match.group('name') + if not JniParams._fully_qualified_class.endswith(inner): + JniParams._inner_classes += [JniParams._fully_qualified_class + '$' + + inner] + + re_additional_imports = re.compile( + r'@JNIAdditionalImport\(\s*{?(?P.*?)}?\s*\)') + for match in re.finditer(re_additional_imports, contents): + for class_name in match.group('class_names').split(','): + JniParams.AddAdditionalImport(class_name.strip()) + + @staticmethod + def ParseJavaPSignature(signature_line): + prefix = 'Signature: ' + index = signature_line.find(prefix) + if index == -1: + prefix = 'descriptor: ' + index = signature_line.index(prefix) + return '"%s"' % signature_line[index + len(prefix):] + + @staticmethod + def JavaToJni(param): + """Converts a java param into a JNI signature type.""" + pod_param_map = { + 'int': 'I', + 'boolean': 'Z', + 'char': 'C', + 'short': 'S', + 'long': 'J', + 'double': 'D', + 'float': 'F', + 'byte': 'B', + 'void': 'V', + } + object_param_list = [ + 'Ljava/lang/Boolean', + 'Ljava/lang/Integer', + 'Ljava/lang/Long', + 'Ljava/lang/Object', + 'Ljava/lang/String', + 'Ljava/lang/Class', + 'Ljava/lang/CharSequence', + 'Ljava/lang/Runnable', + 'Ljava/lang/Throwable', + ] + + prefix = '' + # Array? + while param[-2:] == '[]': + prefix += '[' + param = param[:-2] + # Generic? + if '<' in param: + param = param[:param.index('<')] + if param in pod_param_map: + return prefix + pod_param_map[param] + if '/' in param: + # Coming from javap, use the fully qualified param directly. + return prefix + 'L' + param + ';' + + for qualified_name in (object_param_list + + [JniParams._fully_qualified_class] + + JniParams._inner_classes): + if (qualified_name.endswith('/' + param) or + qualified_name.endswith('$' + param.replace('.', '$')) or + qualified_name == 'L' + param): + return prefix + qualified_name + ';' + + # Is it from an import? (e.g. referecing Class from import pkg.Class; + # note that referencing an inner class Inner from import pkg.Class.Inner + # is not supported). + for qualified_name in JniParams._imports: + if qualified_name.endswith('/' + param): + # Ensure it's not an inner class. + components = qualified_name.split('/') + if len(components) > 2 and components[-2][0].isupper(): + raise SyntaxError('Inner class (%s) can not be imported ' + 'and used by JNI (%s). Please import the outer ' + 'class and use Outer.Inner instead.' % + (qualified_name, param)) + return prefix + qualified_name + ';' + + # Is it an inner class from an outer class import? (e.g. referencing + # Class.Inner from import pkg.Class). + if '.' in param: + components = param.split('.') + outer = '/'.join(components[:-1]) + inner = components[-1] + for qualified_name in JniParams._imports: + if qualified_name.endswith('/' + outer): + return (prefix + qualified_name + '$' + inner + ';') + raise SyntaxError('Inner class (%s) can not be ' + 'used directly by JNI. Please import the outer ' + 'class, probably:\n' + 'import %s.%s;' % + (param, JniParams._package.replace('/', '.'), + outer.replace('/', '.'))) + + JniParams._CheckImplicitImports(param) + + # Type not found, falling back to same package as this class. + return (prefix + 'L' + JniParams._package + '/' + param + ';') + + @staticmethod + def _CheckImplicitImports(param): + # Ensure implicit imports, such as java.lang.*, are not being treated + # as being in the same package. + if not JniParams._implicit_imports: + # This file was generated from android.jar and lists + # all classes that are implicitly imported. + with file(os.path.join(os.path.dirname(sys.argv[0]), + 'android_jar.classes'), 'r') as f: + JniParams._implicit_imports = f.readlines() + for implicit_import in JniParams._implicit_imports: + implicit_import = implicit_import.strip().replace('.class', '') + implicit_import = implicit_import.replace('/', '.') + if implicit_import.endswith('.' + param): + raise SyntaxError('Ambiguous class (%s) can not be used directly ' + 'by JNI.\nPlease import it, probably:\n\n' + 'import %s;' % + (param, implicit_import)) + + + @staticmethod + def Signature(params, returns, wrap): + """Returns the JNI signature for the given datatypes.""" + items = ['('] + items += [JniParams.JavaToJni(param.datatype) for param in params] + items += [')'] + items += [JniParams.JavaToJni(returns)] + if wrap: + return '\n' + '\n'.join(['"' + item + '"' for item in items]) + else: + return '"' + ''.join(items) + '"' + + @staticmethod + def Parse(params): + """Parses the params into a list of Param objects.""" + if not params: + return [] + ret = [] + for p in [p.strip() for p in params.split(',')]: + items = p.split(' ') + + # Remove @Annotations from parameters. + while items[0].startswith('@'): + del items[0] + + if 'final' in items: + items.remove('final') + + param = Param( + datatype=items[0], + name=(items[1] if len(items) > 1 else 'p%s' % len(ret)), + ) + ret += [param] + return ret + + +def ExtractJNINamespace(contents): + re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)') + m = re.findall(re_jni_namespace, contents) + if not m: + return '' + return m[0] + + +def ExtractFullyQualifiedJavaClassName(java_file_name, contents): + re_package = re.compile('.*?package (.*?);') + matches = re.findall(re_package, contents) + if not matches: + raise SyntaxError('Unable to find "package" line in %s' % java_file_name) + return (matches[0].replace('.', '/') + '/' + + os.path.splitext(os.path.basename(java_file_name))[0]) + + +def ExtractNatives(contents, ptr_type): + """Returns a list of dict containing information about a native method.""" + contents = contents.replace('\n', '') + natives = [] + re_native = re.compile(r'(@NativeClassQualifiedName' + '\(\"(?P.*?)\"\)\s+)?' + '(@NativeCall(\(\"(?P.*?)\"\))\s+)?' + '(?P\w+\s\w+|\w+|\s+)\s*native ' + '(?P\S*) ' + '(?Pnative\w+)\((?P.*?)\);') + for match in re.finditer(re_native, contents): + native = NativeMethod( + static='static' in match.group('qualifiers'), + java_class_name=match.group('java_class_name'), + native_class_name=match.group('native_class_name'), + return_type=match.group('return_type'), + name=match.group('name').replace('native', ''), + params=JniParams.Parse(match.group('params')), + ptr_type=ptr_type) + natives += [native] + return natives + + +def IsMainDexJavaClass(contents): + """Returns "true" if the class is annotated with "@MainDex", "false" if not. + + JNI registration doesn't always need to be completed for non-browser processes + since most Java code is only used by the browser process. Classes that are + needed by non-browser processes must explicitly be annotated with @MainDex + to force JNI registration. + """ + re_maindex = re.compile(r'@MainDex[\s\S]*class\s+\w+\s*{') + found = re.search(re_maindex, contents) + return 'true' if found else 'false' + + +def GetStaticCastForReturnType(return_type): + type_map = { 'String' : 'jstring', + 'java/lang/String' : 'jstring', + 'Throwable': 'jthrowable', + 'java/lang/Throwable': 'jthrowable', + 'boolean[]': 'jbooleanArray', + 'byte[]': 'jbyteArray', + 'char[]': 'jcharArray', + 'short[]': 'jshortArray', + 'int[]': 'jintArray', + 'long[]': 'jlongArray', + 'float[]': 'jfloatArray', + 'double[]': 'jdoubleArray' } + ret = type_map.get(return_type, None) + if ret: + return ret + if return_type.endswith('[]'): + return 'jobjectArray' + return None + + +def GetEnvCall(is_constructor, is_static, return_type): + """Maps the types availabe via env->Call__Method.""" + if is_constructor: + return 'NewObject' + env_call_map = {'boolean': 'Boolean', + 'byte': 'Byte', + 'char': 'Char', + 'short': 'Short', + 'int': 'Int', + 'long': 'Long', + 'float': 'Float', + 'void': 'Void', + 'double': 'Double', + 'Object': 'Object', + } + call = env_call_map.get(return_type, 'Object') + if is_static: + call = 'Static' + call + return 'Call' + call + 'Method' + + +def GetMangledParam(datatype): + """Returns a mangled identifier for the datatype.""" + if len(datatype) <= 2: + return datatype.replace('[', 'A') + ret = '' + for i in range(1, len(datatype)): + c = datatype[i] + if c == '[': + ret += 'A' + elif c.isupper() or datatype[i - 1] in ['/', 'L']: + ret += c.upper() + return ret + + +def GetMangledMethodName(name, params, return_type): + """Returns a mangled method name for the given signature. + + The returned name can be used as a C identifier and will be unique for all + valid overloads of the same method. + + Args: + name: string. + params: list of Param. + return_type: string. + + Returns: + A mangled name. + """ + mangled_items = [] + for datatype in [return_type] + [x.datatype for x in params]: + mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))] + mangled_name = name + '_'.join(mangled_items) + assert re.match(r'[0-9a-zA-Z_]+', mangled_name) + return mangled_name + + +def MangleCalledByNatives(called_by_natives): + """Mangles all the overloads from the call_by_natives list.""" + method_counts = collections.defaultdict( + lambda: collections.defaultdict(lambda: 0)) + for called_by_native in called_by_natives: + java_class_name = called_by_native.java_class_name + name = called_by_native.name + method_counts[java_class_name][name] += 1 + for called_by_native in called_by_natives: + java_class_name = called_by_native.java_class_name + method_name = called_by_native.name + method_id_var_name = method_name + if method_counts[java_class_name][method_name] > 1: + method_id_var_name = GetMangledMethodName(method_name, + called_by_native.params, + called_by_native.return_type) + called_by_native.method_id_var_name = method_id_var_name + return called_by_natives + + +# Regex to match the JNI types that should be wrapped in a JavaRef. +RE_SCOPED_JNI_TYPES = re.compile('jobject|jclass|jstring|jthrowable|.*Array') + + +# Regex to match a string like "@CalledByNative public void foo(int bar)". +RE_CALLED_BY_NATIVE = re.compile( + '@CalledByNative(?P(Unchecked)*?)(?:\("(?P.*)"\))?' + '\s+(?P[\w ]*?)' + '(:?\s*@\w+)?' # Ignore annotations in return types. + '\s*(?P\S+?)' + '\s+(?P\w+)' + '\s*\((?P[^\)]*)\)') + + +# Removes empty lines that are indented (i.e. start with 2x spaces). +def RemoveIndentedEmptyLines(string): + return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE) + + +def ExtractCalledByNatives(contents): + """Parses all methods annotated with @CalledByNative. + + Args: + contents: the contents of the java file. + + Returns: + A list of dict with information about the annotated methods. + TODO(bulach): return a CalledByNative object. + + Raises: + ParseError: if unable to parse. + """ + called_by_natives = [] + for match in re.finditer(RE_CALLED_BY_NATIVE, contents): + called_by_natives += [CalledByNative( + system_class=False, + unchecked='Unchecked' in match.group('Unchecked'), + static='static' in match.group('prefix'), + java_class_name=match.group('annotation') or '', + return_type=match.group('return_type'), + name=match.group('name'), + params=JniParams.Parse(match.group('params')))] + # Check for any @CalledByNative occurrences that weren't matched. + unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n') + for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]): + if '@CalledByNative' in line1: + raise ParseError('could not parse @CalledByNative method signature', + line1, line2) + return MangleCalledByNatives(called_by_natives) + + +class JNIFromJavaP(object): + """Uses 'javap' to parse a .class file and generate the JNI header file.""" + + def __init__(self, contents, options): + self.contents = contents + self.namespace = options.namespace + for line in contents: + class_name = re.match( + '.*?(public).*?(class|interface) (?P\S+?)( |\Z)', + line) + if class_name: + self.fully_qualified_class = class_name.group('class_name') + break + self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') + # Java 7's javap includes type parameters in output, like HashSet. Strip + # away the <...> and use the raw class name that Java 6 would've given us. + self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0] + JniParams.SetFullyQualifiedClass(self.fully_qualified_class) + self.java_class_name = self.fully_qualified_class.split('/')[-1] + if not self.namespace: + self.namespace = 'JNI_' + self.java_class_name + re_method = re.compile('(?P.*?)(?P\S+?) (?P\w+?)' + '\((?P.*?)\)') + self.called_by_natives = [] + for lineno, content in enumerate(contents[2:], 2): + match = re.match(re_method, content) + if not match: + continue + self.called_by_natives += [CalledByNative( + system_class=True, + unchecked=False, + static='static' in match.group('prefix'), + java_class_name='', + return_type=match.group('return_type').replace('.', '/'), + name=match.group('name'), + params=JniParams.Parse(match.group('params').replace('.', '/')), + signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))] + re_constructor = re.compile('(.*?)public ' + + self.fully_qualified_class.replace('/', '.') + + '\((?P.*?)\)') + for lineno, content in enumerate(contents[2:], 2): + match = re.match(re_constructor, content) + if not match: + continue + self.called_by_natives += [CalledByNative( + system_class=True, + unchecked=False, + static=False, + java_class_name='', + return_type=self.fully_qualified_class, + name='Constructor', + params=JniParams.Parse(match.group('params').replace('.', '/')), + signature=JniParams.ParseJavaPSignature(contents[lineno + 1]), + is_constructor=True)] + self.called_by_natives = MangleCalledByNatives(self.called_by_natives) + + self.constant_fields = [] + re_constant_field = re.compile('.*?public static final int (?P.*?);') + re_constant_field_value = re.compile( + '.*?Constant(Value| value): int (?P(-*[0-9]+)?)') + for lineno, content in enumerate(contents[2:], 2): + match = re.match(re_constant_field, content) + if not match: + continue + value = re.match(re_constant_field_value, contents[lineno + 2]) + if not value: + value = re.match(re_constant_field_value, contents[lineno + 3]) + if value: + self.constant_fields.append( + ConstantField(name=match.group('name'), + value=value.group('value'))) + + self.inl_header_file_generator = InlHeaderFileGenerator( + self.namespace, self.fully_qualified_class, [], self.called_by_natives, + self.constant_fields, options) + + def GetContent(self): + return self.inl_header_file_generator.GetContent() + + @staticmethod + def CreateFromClass(class_file, options): + class_name = os.path.splitext(os.path.basename(class_file))[0] + p = subprocess.Popen(args=[options.javap, '-c', '-verbose', + '-s', class_name], + cwd=os.path.dirname(class_file), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, _ = p.communicate() + jni_from_javap = JNIFromJavaP(stdout.split('\n'), options) + return jni_from_javap + + +class JNIFromJavaSource(object): + """Uses the given java source file to generate the JNI header file.""" + + # Match single line comments, multiline comments, character literals, and + # double-quoted strings. + _comment_remover_regex = re.compile( + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', + re.DOTALL | re.MULTILINE) + + def __init__(self, contents, fully_qualified_class, options): + contents = self._RemoveComments(contents) + JniParams.SetFullyQualifiedClass(fully_qualified_class) + JniParams.ExtractImportsAndInnerClasses(contents) + jni_namespace = ExtractJNINamespace(contents) or options.namespace + natives = ExtractNatives(contents, options.ptr_type) + called_by_natives = ExtractCalledByNatives(contents) + maindex = IsMainDexJavaClass(contents) + if len(natives) == 0 and len(called_by_natives) == 0: + raise SyntaxError('Unable to find any JNI methods for %s.' % + fully_qualified_class) + inl_header_file_generator = InlHeaderFileGenerator( + jni_namespace, fully_qualified_class, natives, called_by_natives, [], + options, maindex) + self.content = inl_header_file_generator.GetContent() + + @classmethod + def _RemoveComments(cls, contents): + # We need to support both inline and block comments, and we need to handle + # strings that contain '//' or '/*'. + # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java + # parser. Maybe we could ditch JNIFromJavaSource and just always use + # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT. + # http://code.google.com/p/chromium/issues/detail?id=138941 + def replacer(match): + # Replace matches that are comments with nothing; return literals/strings + # unchanged. + s = match.group(0) + if s.startswith('/'): + return '' + else: + return s + return cls._comment_remover_regex.sub(replacer, contents) + + def GetContent(self): + return self.content + + @staticmethod + def CreateFromFile(java_file_name, options): + contents = file(java_file_name).read() + fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name, + contents) + return JNIFromJavaSource(contents, fully_qualified_class, options) + + +class InlHeaderFileGenerator(object): + """Generates an inline header file for JNI integration.""" + + def __init__(self, namespace, fully_qualified_class, natives, + called_by_natives, constant_fields, options, maindex='false'): + self.namespace = namespace + self.fully_qualified_class = fully_qualified_class + self.class_name = self.fully_qualified_class.split('/')[-1] + self.natives = natives + self.called_by_natives = called_by_natives + self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' + self.constant_fields = constant_fields + self.maindex = maindex + self.options = options + + + def GetContent(self): + """Returns the content of the JNI binding file.""" + template = Template("""\ +// Copyright 2014 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. + + +// This file is autogenerated by +// ${SCRIPT_NAME} +// For +// ${FULLY_QUALIFIED_CLASS} + +#ifndef ${HEADER_GUARD} +#define ${HEADER_GUARD} + +#include + +${INCLUDES} + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +$CLASS_PATH_DEFINITIONS + +} // namespace + +$OPEN_NAMESPACE + +$CONSTANT_FIELDS + +// Step 2: method stubs. +$METHOD_STUBS + +// Step 3: RegisterNatives. +$JNI_NATIVE_METHODS +$REGISTER_NATIVES +$CLOSE_NAMESPACE + +#endif // ${HEADER_GUARD} +""") + values = { + 'SCRIPT_NAME': self.options.script_name, + 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, + 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), + 'CONSTANT_FIELDS': self.GetConstantFieldsString(), + 'METHOD_STUBS': self.GetMethodStubsString(), + 'OPEN_NAMESPACE': self.GetOpenNamespaceString(), + 'JNI_NATIVE_METHODS': self.GetJNINativeMethodsString(), + 'REGISTER_NATIVES': self.GetRegisterNativesString(), + 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(), + 'HEADER_GUARD': self.header_guard, + 'INCLUDES': self.GetIncludesString(), + } + assert ((values['JNI_NATIVE_METHODS'] == '') == + (values['REGISTER_NATIVES'] == '')) + return WrapOutput(template.substitute(values)) + + def GetClassPathDefinitionsString(self): + ret = [] + ret += [self.GetClassPathDefinitions()] + return '\n'.join(ret) + + def GetConstantFieldsString(self): + if not self.constant_fields: + return '' + ret = ['enum Java_%s_constant_fields {' % self.class_name] + for c in self.constant_fields: + ret += [' %s = %s,' % (c.name, c.value)] + ret += ['};'] + return '\n'.join(ret) + + def GetMethodStubsString(self): + """Returns the code corresponding to method stubs.""" + ret = [] + for native in self.natives: + ret += [self.GetNativeStub(native)] + ret += self.GetLazyCalledByNativeMethodStubs() + return '\n'.join(ret) + + def GetLazyCalledByNativeMethodStubs(self): + return [self.GetLazyCalledByNativeMethodStub(called_by_native) + for called_by_native in self.called_by_natives] + + def GetIncludesString(self): + if not self.options.includes: + return '' + includes = self.options.includes.split(',') + return '\n'.join('#include "%s"' % x for x in includes) + + def GetKMethodsString(self, clazz): + ret = [] + for native in self.natives: + if (native.java_class_name == clazz or + (not native.java_class_name and clazz == self.class_name)): + ret += [self.GetKMethodArrayEntry(native)] + return '\n'.join(ret) + + def SubstituteNativeMethods(self, template): + """Substitutes JAVA_CLASS and KMETHODS in the provided template.""" + ret = [] + all_classes = self.GetUniqueClasses(self.natives) + all_classes[self.class_name] = self.fully_qualified_class + for clazz in all_classes: + kmethods = self.GetKMethodsString(clazz) + if kmethods: + values = {'JAVA_CLASS': clazz, + 'KMETHODS': kmethods} + ret += [template.substitute(values)] + if not ret: return '' + return '\n' + '\n'.join(ret) + + def GetJNINativeMethodsString(self): + """Returns the implementation of the array of native methods.""" + if not self.options.native_exports_optional: + return '' + template = Template("""\ +static const JNINativeMethod kMethods${JAVA_CLASS}[] = { +${KMETHODS} +}; +""") + return self.SubstituteNativeMethods(template) + + def GetRegisterNativesString(self): + """Returns the code for RegisterNatives.""" + natives = self.GetRegisterNativesImplString() + if not natives: + return '' + + template = Template("""\ +${REGISTER_NATIVES_SIGNATURE} { +${EARLY_EXIT} +${NATIVES} + return true; +} +""") + signature = 'static bool RegisterNativesImpl(JNIEnv* env)' + early_exit = '' + if self.options.native_exports_optional: + early_exit = """\ + if (jni_generator::ShouldSkipJniRegistration(%s)) + return true; +""" % self.maindex + + values = {'REGISTER_NATIVES_SIGNATURE': signature, + 'EARLY_EXIT': early_exit, + 'NATIVES': natives, + } + + return template.substitute(values) + + def GetRegisterNativesImplString(self): + """Returns the shared implementation for RegisterNatives.""" + if not self.options.native_exports_optional: + return '' + + template = Template("""\ + const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS}); + + if (env->RegisterNatives(${JAVA_CLASS}_clazz(env), + kMethods${JAVA_CLASS}, + kMethods${JAVA_CLASS}Size) < 0) { + jni_generator::HandleRegistrationError( + env, ${JAVA_CLASS}_clazz(env), __FILE__); + return false; + } +""") + return self.SubstituteNativeMethods(template) + + def GetOpenNamespaceString(self): + if self.namespace: + all_namespaces = ['namespace %s {' % ns + for ns in self.namespace.split('::')] + return '\n'.join(all_namespaces) + return '' + + def GetCloseNamespaceString(self): + if self.namespace: + all_namespaces = ['} // namespace %s' % ns + for ns in self.namespace.split('::')] + all_namespaces.reverse() + return '\n'.join(all_namespaces) + '\n' + return '' + + def GetJNIFirstParamType(self, native): + if native.type == 'method': + return 'jobject' + elif native.type == 'function': + if native.static: + return 'jclass' + else: + return 'jobject' + + def GetJNIFirstParam(self, native, for_declaration): + c_type = self.GetJNIFirstParamType(native) + if for_declaration: + c_type = WrapCTypeForDeclaration(c_type) + return [c_type + ' jcaller'] + + def GetParamsInDeclaration(self, native): + """Returns the params for the forward declaration. + + Args: + native: the native dictionary describing the method. + + Returns: + A string containing the params. + """ + return ',\n '.join(self.GetJNIFirstParam(native, True) + + [JavaDataTypeToCForDeclaration(param.datatype) + ' ' + + param.name + for param in native.params]) + + def GetParamsInStub(self, native): + """Returns the params for the stub declaration. + + Args: + native: the native dictionary describing the method. + + Returns: + A string containing the params. + """ + return ',\n '.join(self.GetJNIFirstParam(native, False) + + [JavaDataTypeToC(param.datatype) + ' ' + + param.name + for param in native.params]) + + def GetCalledByNativeParamsInDeclaration(self, called_by_native): + return ',\n '.join([ + JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' + + param.name + for param in called_by_native.params]) + + def GetStubName(self, native): + """Return the name of the stub function for this native method. + + Args: + native: the native dictionary describing the method. + + Returns: + A string with the stub function name (used by the JVM). + """ + template = Template("Java_${JAVA_NAME}_native${NAME}") + + java_name = self.fully_qualified_class.replace('_', '_1').replace('/', '_') + if native.java_class_name: + java_name += '_00024' + native.java_class_name + + values = {'NAME': native.name, + 'JAVA_NAME': java_name} + return template.substitute(values) + + def GetJavaParamRefForCall(self, c_type, name): + return Template( + 'base::android::JavaParamRef<${TYPE}>(env, ${NAME})').substitute({ + 'TYPE': c_type, + 'NAME': name, + }) + + def GetJNIFirstParamForCall(self, native): + c_type = self.GetJNIFirstParamType(native) + return [self.GetJavaParamRefForCall(c_type, 'jcaller')] + + def GetNativeStub(self, native): + is_method = native.type == 'method' + + if is_method: + params = native.params[1:] + else: + params = native.params + params_in_call = ['env'] + self.GetJNIFirstParamForCall(native) + for p in params: + c_type = JavaDataTypeToC(p.datatype) + if re.match(RE_SCOPED_JNI_TYPES, c_type): + params_in_call.append(self.GetJavaParamRefForCall(c_type, p.name)) + else: + params_in_call.append(p.name) + params_in_call = ', '.join(params_in_call) + + return_type = return_declaration = JavaDataTypeToC(native.return_type) + post_call = '' + if re.match(RE_SCOPED_JNI_TYPES, return_type): + post_call = '.Release()' + return_declaration = ('base::android::ScopedJavaLocalRef<' + return_type + + '>') + profiling_entered_native = '' + if self.options.enable_profiling: + profiling_entered_native = 'JNI_LINK_SAVED_FRAME_POINTER;' + values = { + 'RETURN': return_type, + 'RETURN_DECLARATION': return_declaration, + 'NAME': native.name, + 'PARAMS': self.GetParamsInDeclaration(native), + 'PARAMS_IN_STUB': self.GetParamsInStub(native), + 'PARAMS_IN_CALL': params_in_call, + 'POST_CALL': post_call, + 'STUB_NAME': self.GetStubName(native), + 'PROFILING_ENTERED_NATIVE': profiling_entered_native, + } + + if is_method: + optional_error_return = JavaReturnValueToC(native.return_type) + if optional_error_return: + optional_error_return = ', ' + optional_error_return + values.update({ + 'OPTIONAL_ERROR_RETURN': optional_error_return, + 'PARAM0_NAME': native.params[0].name, + 'P0_TYPE': native.p0_type, + }) + template = Template("""\ +JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) { + ${PROFILING_ENTERED_NATIVE} + ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME}); + CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN}); + return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL}; +} +""") + else: + template = Template(""" +static ${RETURN_DECLARATION} ${NAME}(JNIEnv* env, ${PARAMS}); + +JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) { + ${PROFILING_ENTERED_NATIVE} + return ${NAME}(${PARAMS_IN_CALL})${POST_CALL}; +} +""") + + return RemoveIndentedEmptyLines(template.substitute(values)) + + def GetArgument(self, param): + if param.datatype == 'int': + return 'as_jint(' + param.name + ')' + elif re.match(RE_SCOPED_JNI_TYPES, JavaDataTypeToC(param.datatype)): + return param.name + '.obj()' + else: + return param.name + + def GetArgumentsInCall(self, params): + """Return a string of arguments to call from native into Java""" + return [self.GetArgument(p) for p in params] + + def GetCalledByNativeValues(self, called_by_native): + """Fills in necessary values for the CalledByNative methods.""" + java_class = called_by_native.java_class_name or self.class_name + if called_by_native.static or called_by_native.is_constructor: + first_param_in_declaration = '' + first_param_in_call = ('%s_clazz(env)' % java_class) + else: + first_param_in_declaration = ( + ', const base::android::JavaRefOrBare& obj') + first_param_in_call = 'obj.obj()' + params_in_declaration = self.GetCalledByNativeParamsInDeclaration( + called_by_native) + if params_in_declaration: + params_in_declaration = ', ' + params_in_declaration + params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params)) + if params_in_call: + params_in_call = ', ' + params_in_call + pre_call = '' + post_call = '' + if called_by_native.static_cast: + pre_call = 'static_cast<%s>(' % called_by_native.static_cast + post_call = ')' + check_exception = '' + if not called_by_native.unchecked: + check_exception = 'jni_generator::CheckException(env);' + return_type = JavaDataTypeToC(called_by_native.return_type) + optional_error_return = JavaReturnValueToC(called_by_native.return_type) + if optional_error_return: + optional_error_return = ', ' + optional_error_return + return_declaration = '' + return_clause = '' + if return_type != 'void': + pre_call = ' ' + pre_call + return_declaration = return_type + ' ret =' + if re.match(RE_SCOPED_JNI_TYPES, return_type): + return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>' + return_clause = 'return ' + return_type + '(env, ret);' + else: + return_clause = 'return ret;' + profiling_leaving_native = '' + if self.options.enable_profiling: + profiling_leaving_native = 'JNI_SAVE_FRAME_POINTER;' + return { + 'JAVA_CLASS': java_class, + 'RETURN_TYPE': return_type, + 'OPTIONAL_ERROR_RETURN': optional_error_return, + 'RETURN_DECLARATION': return_declaration, + 'RETURN_CLAUSE': return_clause, + 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration, + 'PARAMS_IN_DECLARATION': params_in_declaration, + 'PRE_CALL': pre_call, + 'POST_CALL': post_call, + 'ENV_CALL': called_by_native.env_call, + 'FIRST_PARAM_IN_CALL': first_param_in_call, + 'PARAMS_IN_CALL': params_in_call, + 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, + 'CHECK_EXCEPTION': check_exception, + 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native), + 'PROFILING_LEAVING_NATIVE': profiling_leaving_native, + } + + + def GetLazyCalledByNativeMethodStub(self, called_by_native): + """Returns a string.""" + function_signature_template = Template("""\ +static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\ +JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""") + function_header_template = Template("""\ +${FUNCTION_SIGNATURE} {""") + function_header_with_unused_template = Template("""\ +${FUNCTION_SIGNATURE} __attribute__ ((unused)); +${FUNCTION_SIGNATURE} {""") + template = Template(""" +static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0; +${FUNCTION_HEADER} + CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL}, + ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN}); + jmethodID method_id = + ${GET_METHOD_ID_IMPL} + ${PROFILING_LEAVING_NATIVE} + ${RETURN_DECLARATION} + ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL}, + method_id${PARAMS_IN_CALL})${POST_CALL}; + ${CHECK_EXCEPTION} + ${RETURN_CLAUSE} +}""") + values = self.GetCalledByNativeValues(called_by_native) + values['FUNCTION_SIGNATURE'] = ( + function_signature_template.substitute(values)) + if called_by_native.system_class: + values['FUNCTION_HEADER'] = ( + function_header_with_unused_template.substitute(values)) + else: + values['FUNCTION_HEADER'] = function_header_template.substitute(values) + return RemoveIndentedEmptyLines(template.substitute(values)) + + def GetKMethodArrayEntry(self, native): + template = Template(' { "native${NAME}", ${JNI_SIGNATURE}, ' + + 'reinterpret_cast(${STUB_NAME}) },') + values = {'NAME': native.name, + 'JNI_SIGNATURE': JniParams.Signature(native.params, + native.return_type, + True), + 'STUB_NAME': self.GetStubName(native)} + return template.substitute(values) + + def GetUniqueClasses(self, origin): + ret = {self.class_name: self.fully_qualified_class} + for entry in origin: + class_name = self.class_name + jni_class_path = self.fully_qualified_class + if entry.java_class_name: + class_name = entry.java_class_name + jni_class_path = self.fully_qualified_class + '$' + class_name + ret[class_name] = jni_class_path + return ret + + def GetClassPathDefinitions(self): + """Returns the ClassPath constants.""" + ret = [] + template = Template("""\ +const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""") + all_classes = self.GetUniqueClasses(self.called_by_natives) + if self.options.native_exports_optional: + all_classes.update(self.GetUniqueClasses(self.natives)) + + for clazz in all_classes: + values = { + 'JAVA_CLASS': clazz, + 'JNI_CLASS_PATH': all_classes[clazz], + } + ret += [template.substitute(values)] + ret += '' + + template = Template("""\ +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_${JAVA_CLASS}_clazz __attribute__((unused)) = 0; +#define ${JAVA_CLASS}_clazz(env) \ +base::android::LazyGetClass(env, k${JAVA_CLASS}ClassPath, \ +&g_${JAVA_CLASS}_clazz)""") + + for clazz in all_classes: + values = { + 'JAVA_CLASS': clazz, + } + ret += [template.substitute(values)] + + return '\n'.join(ret) + + def GetMethodIDImpl(self, called_by_native): + """Returns the implementation of GetMethodID.""" + template = Template("""\ + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_${STATIC}>( + env, ${JAVA_CLASS}_clazz(env), + "${JNI_NAME}", + ${JNI_SIGNATURE}, + &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}); +""") + jni_name = called_by_native.name + jni_return_type = called_by_native.return_type + if called_by_native.is_constructor: + jni_name = '' + jni_return_type = 'void' + if called_by_native.signature: + signature = called_by_native.signature + else: + signature = JniParams.Signature(called_by_native.params, + jni_return_type, + True) + values = { + 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, + 'JNI_NAME': jni_name, + 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, + 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE', + 'JNI_SIGNATURE': signature, + } + return template.substitute(values) + + +def WrapOutput(output): + ret = [] + for line in output.splitlines(): + # Do not wrap lines under 80 characters or preprocessor directives. + if len(line) < 80 or line.lstrip()[:1] == '#': + stripped = line.rstrip() + if len(ret) == 0 or len(ret[-1]) or len(stripped): + ret.append(stripped) + else: + first_line_indent = ' ' * (len(line) - len(line.lstrip())) + subsequent_indent = first_line_indent + ' ' * 4 + if line.startswith('//'): + subsequent_indent = '//' + subsequent_indent + wrapper = textwrap.TextWrapper(width=80, + subsequent_indent=subsequent_indent, + break_long_words=False) + ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)] + ret += [''] + return '\n'.join(ret) + + +def ExtractJarInputFile(jar_file, input_file, out_dir): + """Extracts input file from jar and returns the filename. + + The input file is extracted to the same directory that the generated jni + headers will be placed in. This is passed as an argument to script. + + Args: + jar_file: the jar file containing the input files to extract. + input_files: the list of files to extract from the jar file. + out_dir: the name of the directories to extract to. + + Returns: + the name of extracted input file. + """ + jar_file = zipfile.ZipFile(jar_file) + + out_dir = os.path.join(out_dir, os.path.dirname(input_file)) + try: + os.makedirs(out_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + extracted_file_name = os.path.join(out_dir, os.path.basename(input_file)) + with open(extracted_file_name, 'w') as outfile: + outfile.write(jar_file.read(input_file)) + + return extracted_file_name + + +def GenerateJNIHeader(input_file, output_file, options): + try: + if os.path.splitext(input_file)[1] == '.class': + jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options) + content = jni_from_javap.GetContent() + else: + jni_from_java_source = JNIFromJavaSource.CreateFromFile( + input_file, options) + content = jni_from_java_source.GetContent() + except ParseError, e: + print e + sys.exit(1) + if output_file: + if not os.path.exists(os.path.dirname(os.path.abspath(output_file))): + os.makedirs(os.path.dirname(os.path.abspath(output_file))) + if options.optimize_generation and os.path.exists(output_file): + with file(output_file, 'r') as f: + existing_content = f.read() + if existing_content == content: + return + with file(output_file, 'w') as f: + f.write(content) + else: + print content + + +def GetScriptName(): + script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) + base_index = 0 + for idx, value in enumerate(script_components): + if value == 'base' or value == 'third_party': + base_index = idx + break + return os.sep.join(script_components[base_index:]) + + +def main(argv): + usage = """usage: %prog [OPTIONS] +This script will parse the given java source code extracting the native +declarations and print the header file to stdout (or a file). +See SampleForTests.java for more details. + """ + option_parser = optparse.OptionParser(usage=usage) + build_utils.AddDepfileOption(option_parser) + + option_parser.add_option('-j', '--jar_file', dest='jar_file', + help='Extract the list of input files from' + ' a specified jar file.' + ' Uses javap to extract the methods from a' + ' pre-compiled class. --input should point' + ' to pre-compiled Java .class files.') + option_parser.add_option('-n', dest='namespace', + help='Uses as a namespace in the generated header ' + 'instead of the javap class name, or when there is ' + 'no JNINamespace annotation in the java source.') + option_parser.add_option('--input_file', + help='Single input file name. The output file name ' + 'will be derived from it. Must be used with ' + '--output_dir.') + option_parser.add_option('--output_dir', + help='The output directory. Must be used with ' + '--input') + option_parser.add_option('--optimize_generation', type="int", + default=0, help='Whether we should optimize JNI ' + 'generation by not regenerating files if they have ' + 'not changed.') + option_parser.add_option('--script_name', default=GetScriptName(), + help='The name of this script in the generated ' + 'header.') + option_parser.add_option('--includes', + help='The comma-separated list of header files to ' + 'include in the generated header.') + option_parser.add_option('--ptr_type', default='int', + type='choice', choices=['int', 'long'], + help='The type used to represent native pointers in ' + 'Java code. For 32-bit, use int; ' + 'for 64-bit, use long.') + option_parser.add_option('--cpp', default='cpp', + help='The path to cpp command.') + option_parser.add_option('--javap', default='javap', + help='The path to javap command.') + option_parser.add_option('--native_exports_optional', action='store_true', + help='Support both explicit and native method' + 'registration.') + option_parser.add_option('--enable_profiling', action='store_true', + help='Add additional profiling instrumentation.') + options, args = option_parser.parse_args(argv) + if options.jar_file: + input_file = ExtractJarInputFile(options.jar_file, options.input_file, + options.output_dir) + elif options.input_file: + input_file = options.input_file + else: + option_parser.print_help() + print '\nError: Must specify --jar_file or --input_file.' + return 1 + output_file = None + if options.output_dir: + root_name = os.path.splitext(os.path.basename(input_file))[0] + output_file = os.path.join(options.output_dir, root_name) + '_jni.h' + GenerateJNIHeader(input_file, output_file, options) + + if options.depfile: + build_utils.WriteDepfile(options.depfile, output_file) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/base/android/jni_generator/jni_generator_helper.h b/base/android/jni_generator/jni_generator_helper.h new file mode 100644 index 0000000000000000000000000000000000000000..3062806a85fd7f08732e88ff4b7e85c6fd6f0609 --- /dev/null +++ b/base/android/jni_generator/jni_generator_helper.h @@ -0,0 +1,62 @@ +// Copyright 2014 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 BASE_ANDROID_JNI_GENERATOR_JNI_GENERATOR_HELPER_H_ +#define BASE_ANDROID_JNI_GENERATOR_JNI_GENERATOR_HELPER_H_ + +#include + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/logging.h" +#include "build/build_config.h" + +// Project-specific macros used by the header files generated by +// jni_generator.py. Different projects can then specify their own +// implementation for this file. +#define CHECK_NATIVE_PTR(env, jcaller, native_ptr, method_name, ...) \ + DCHECK(native_ptr) << method_name; + +#define CHECK_CLAZZ(env, jcaller, clazz, ...) DCHECK(clazz); + +#if defined(ARCH_CPU_X86) +// Dalvik JIT generated code doesn't guarantee 16-byte stack alignment on +// x86 - use force_align_arg_pointer to realign the stack at the JNI +// boundary. crbug.com/655248 +#define JNI_GENERATOR_EXPORT \ + extern "C" __attribute__((visibility("default"), force_align_arg_pointer)) +#else +#define JNI_GENERATOR_EXPORT extern "C" __attribute__((visibility("default"))) +#endif + +namespace jni_generator { + +inline void HandleRegistrationError(JNIEnv* env, + jclass clazz, + const char* filename) { + LOG(ERROR) << "RegisterNatives failed in " << filename; +} + +inline void CheckException(JNIEnv* env) { + base::android::CheckException(env); +} + +inline bool ShouldSkipJniRegistration(bool is_maindex_class) { + switch (base::android::GetJniRegistrationType()) { + case base::android::ALL_JNI_REGISTRATION: + return false; + case base::android::NO_JNI_REGISTRATION: + // TODO(estevenson): Change this to a DCHECK. + return true; + case base::android::SELECTIVE_JNI_REGISTRATION: + return !is_maindex_class; + default: + NOTREACHED(); + return false; + } +} + +} // namespace jni_generator + +#endif // BASE_ANDROID_JNI_GENERATOR_JNI_GENERATOR_HELPER_H_ diff --git a/base/android/jni_generator/jni_generator_tests.py b/base/android/jni_generator/jni_generator_tests.py new file mode 100755 index 0000000000000000000000000000000000000000..c0c82388dc0024c8636839e0d62a76776165e16b --- /dev/null +++ b/base/android/jni_generator/jni_generator_tests.py @@ -0,0 +1,1097 @@ +#!/usr/bin/env python +# Copyright (c) 2012 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 jni_generator.py. + +This test suite contains various tests for the JNI generator. +It exercises the low-level parser all the way up to the +code generator and ensures the output matches a golden +file. +""" + +import difflib +import inspect +import optparse +import os +import sys +import unittest +import jni_generator +from jni_generator import CalledByNative, JniParams, NativeMethod, Param + + +SCRIPT_NAME = 'base/android/jni_generator/jni_generator.py' +INCLUDES = ( + 'base/android/jni_generator/jni_generator_helper.h' +) + +# Set this environment variable in order to regenerate the golden text +# files. +REBASELINE_ENV = 'REBASELINE' + +class TestOptions(object): + """The mock options object which is passed to the jni_generator.py script.""" + + def __init__(self): + self.namespace = None + self.script_name = SCRIPT_NAME + self.includes = INCLUDES + self.ptr_type = 'long' + self.cpp = 'cpp' + self.javap = 'javap' + self.native_exports_optional = True + self.enable_profiling = False + +class TestGenerator(unittest.TestCase): + def assertObjEquals(self, first, second): + dict_first = first.__dict__ + dict_second = second.__dict__ + self.assertEquals(dict_first.keys(), dict_second.keys()) + for key, value in dict_first.iteritems(): + if (type(value) is list and len(value) and + isinstance(type(value[0]), object)): + self.assertListEquals(value, second.__getattribute__(key)) + else: + actual = second.__getattribute__(key) + self.assertEquals(value, actual, + 'Key ' + key + ': ' + str(value) + '!=' + str(actual)) + + def assertListEquals(self, first, second): + self.assertEquals(len(first), len(second)) + for i in xrange(len(first)): + if isinstance(first[i], object): + self.assertObjEquals(first[i], second[i]) + else: + self.assertEquals(first[i], second[i]) + + def assertTextEquals(self, golden_text, generated_text): + if not self.compareText(golden_text, generated_text): + self.fail('Golden text mismatch.') + + def compareText(self, golden_text, generated_text): + def FilterText(text): + return [ + l.strip() for l in text.split('\n') + if not l.startswith('// Copyright') + ] + stripped_golden = FilterText(golden_text) + stripped_generated = FilterText(generated_text) + if stripped_golden == stripped_generated: + return True + print self.id() + for line in difflib.context_diff(stripped_golden, stripped_generated): + print line + print '\n\nGenerated' + print '=' * 80 + print generated_text + print '=' * 80 + print 'Run with:' + print 'REBASELINE=1', sys.argv[0] + print 'to regenerate the data files.' + + def _ReadGoldenFile(self, golden_file): + if not os.path.exists(golden_file): + return None + with file(golden_file, 'r') as f: + return f.read() + + def assertGoldenTextEquals(self, generated_text): + script_dir = os.path.dirname(sys.argv[0]) + # This is the caller test method. + caller = inspect.stack()[1][3] + self.assertTrue(caller.startswith('test'), + 'assertGoldenTextEquals can only be called from a ' + 'test* method, not %s' % caller) + golden_file = os.path.join(script_dir, caller + '.golden') + golden_text = self._ReadGoldenFile(golden_file) + if os.environ.get(REBASELINE_ENV): + if golden_text != generated_text: + with file(golden_file, 'w') as f: + f.write(generated_text) + return + self.assertTextEquals(golden_text, generated_text) + + def testInspectCaller(self): + def willRaise(): + # This function can only be called from a test* method. + self.assertGoldenTextEquals('') + self.assertRaises(AssertionError, willRaise) + + def testNatives(self): + test_data = """" + interface OnFrameAvailableListener {} + private native int nativeInit(); + private native void nativeDestroy(int nativeChromeBrowserProvider); + private native long nativeAddBookmark( + int nativeChromeBrowserProvider, + String url, String title, boolean isFolder, long parentId); + private static native String nativeGetDomainAndRegistry(String url); + private static native void nativeCreateHistoricalTabFromState( + byte[] state, int tab_index); + private native byte[] nativeGetStateAsByteArray(View view); + private static native String[] nativeGetAutofillProfileGUIDs(); + private native void nativeSetRecognitionResults( + int sessionId, String[] results); + private native long nativeAddBookmarkFromAPI( + int nativeChromeBrowserProvider, + String url, Long created, Boolean isBookmark, + Long date, byte[] favicon, String title, Integer visits); + native int nativeFindAll(String find); + private static native OnFrameAvailableListener nativeGetInnerClass(); + private native Bitmap nativeQueryBitmap( + int nativeChromeBrowserProvider, + String[] projection, String selection, + String[] selectionArgs, String sortOrder); + private native void nativeGotOrientation( + int nativeDataFetcherImplAndroid, + double alpha, double beta, double gamma); + private static native Throwable nativeMessWithJavaException(Throwable e); + """ + jni_generator.JniParams.SetFullyQualifiedClass( + 'org/chromium/example/jni_generator/SampleForTests') + jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data) + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', + params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=False, name='Destroy', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='long', static=False, name='AddBookmark', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String', + name='url'), + Param(datatype='String', + name='title'), + Param(datatype='boolean', + name='isFolder'), + Param(datatype='long', + name='parentId')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='String', static=True, + name='GetDomainAndRegistry', + params=[Param(datatype='String', + name='url')], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=True, + name='CreateHistoricalTabFromState', + params=[Param(datatype='byte[]', + name='state'), + Param(datatype='int', + name='tab_index')], + java_class_name=None, + type='function'), + NativeMethod(return_type='byte[]', static=False, + name='GetStateAsByteArray', + params=[Param(datatype='View', name='view')], + java_class_name=None, + type='function'), + NativeMethod(return_type='String[]', static=True, + name='GetAutofillProfileGUIDs', params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=False, + name='SetRecognitionResults', + params=[Param(datatype='int', name='sessionId'), + Param(datatype='String[]', name='results')], + java_class_name=None, + type='function'), + NativeMethod(return_type='long', static=False, + name='AddBookmarkFromAPI', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String', + name='url'), + Param(datatype='Long', + name='created'), + Param(datatype='Boolean', + name='isBookmark'), + Param(datatype='Long', + name='date'), + Param(datatype='byte[]', + name='favicon'), + Param(datatype='String', + name='title'), + Param(datatype='Integer', + name='visits')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='int', static=False, + name='FindAll', + params=[Param(datatype='String', + name='find')], + java_class_name=None, + type='function'), + NativeMethod(return_type='OnFrameAvailableListener', static=True, + name='GetInnerClass', + params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='Bitmap', + static=False, + name='QueryBitmap', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String[]', + name='projection'), + Param(datatype='String', + name='selection'), + Param(datatype='String[]', + name='selectionArgs'), + Param(datatype='String', + name='sortOrder'), + ], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='void', static=False, + name='GotOrientation', + params=[Param(datatype='int', + name='nativeDataFetcherImplAndroid'), + Param(datatype='double', + name='alpha'), + Param(datatype='double', + name='beta'), + Param(datatype='double', + name='gamma'), + ], + java_class_name=None, + type='method', + p0_type='content::DataFetcherImplAndroid'), + NativeMethod(return_type='Throwable', static=True, + name='MessWithJavaException', + params=[Param(datatype='Throwable', name='e')], + java_class_name=None, + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testInnerClassNatives(self): + test_data = """ + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + """ + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testInnerClassNativesMultiple(self): + test_data = """ + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + """ + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyInnerClass', + type='function'), + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyOtherInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testInnerClassNativesBothInnerAndOuter(self): + test_data = """ + class MyOuterClass { + private native int nativeInit(); + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + } + """ + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyOtherInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testCalledByNatives(self): + test_data = """" + import android.graphics.Bitmap; + import android.view.View; + import java.io.InputStream; + import java.util.List; + + class InnerClass {} + + @CalledByNative + InnerClass showConfirmInfoBar(int nativeInfoBar, + String buttonOk, String buttonCancel, String title, Bitmap icon) { + InfoBar infobar = new ConfirmInfoBar(nativeInfoBar, mContext, + buttonOk, buttonCancel, + title, icon); + return infobar; + } + @CalledByNative + InnerClass showAutoLoginInfoBar(int nativeInfoBar, + String realm, String account, String args) { + AutoLoginInfoBar infobar = new AutoLoginInfoBar(nativeInfoBar, mContext, + realm, account, args); + if (infobar.displayedAccountCount() == 0) + infobar = null; + return infobar; + } + @CalledByNative("InfoBar") + void dismiss(); + @SuppressWarnings("unused") + @CalledByNative + private static boolean shouldShowAutoLogin(View view, + String realm, String account, String args) { + AccountManagerContainer accountManagerContainer = + new AccountManagerContainer((Activity)contentView.getContext(), + realm, account, args); + String[] logins = accountManagerContainer.getAccountLogins(null); + return logins.length != 0; + } + @CalledByNative + static InputStream openUrl(String url) { + return null; + } + @CalledByNative + private void activateHardwareAcceleration(final boolean activated, + final int iPid, final int iType, + final int iPrimaryID, final int iSecondaryID) { + if (!activated) { + return + } + } + @CalledByNative + public static @Status int updateStatus(@Status int status) { + return getAndUpdateStatus(status); + } + @CalledByNativeUnchecked + private void uncheckedCall(int iParam); + + @CalledByNative + public byte[] returnByteArray(); + + @CalledByNative + public boolean[] returnBooleanArray(); + + @CalledByNative + public char[] returnCharArray(); + + @CalledByNative + public short[] returnShortArray(); + + @CalledByNative + public int[] returnIntArray(); + + @CalledByNative + public long[] returnLongArray(); + + @CalledByNative + public double[] returnDoubleArray(); + + @CalledByNative + public Object[] returnObjectArray(); + + @CalledByNative + public byte[][] returnArrayOfByteArray(); + + @CalledByNative + public Bitmap.CompressFormat getCompressFormat(); + + @CalledByNative + public List getCompressFormatList(); + """ + jni_generator.JniParams.SetFullyQualifiedClass('org/chromium/Foo') + jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data) + called_by_natives = jni_generator.ExtractCalledByNatives(test_data) + golden_called_by_natives = [ + CalledByNative( + return_type='InnerClass', + system_class=False, + static=False, + name='showConfirmInfoBar', + method_id_var_name='showConfirmInfoBar', + java_class_name='', + params=[Param(datatype='int', name='nativeInfoBar'), + Param(datatype='String', name='buttonOk'), + Param(datatype='String', name='buttonCancel'), + Param(datatype='String', name='title'), + Param(datatype='Bitmap', name='icon')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='InnerClass', + system_class=False, + static=False, + name='showAutoLoginInfoBar', + method_id_var_name='showAutoLoginInfoBar', + java_class_name='', + params=[Param(datatype='int', name='nativeInfoBar'), + Param(datatype='String', name='realm'), + Param(datatype='String', name='account'), + Param(datatype='String', name='args')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='dismiss', + method_id_var_name='dismiss', + java_class_name='InfoBar', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='boolean', + system_class=False, + static=True, + name='shouldShowAutoLogin', + method_id_var_name='shouldShowAutoLogin', + java_class_name='', + params=[Param(datatype='View', name='view'), + Param(datatype='String', name='realm'), + Param(datatype='String', name='account'), + Param(datatype='String', name='args')], + env_call=('Boolean', ''), + unchecked=False, + ), + CalledByNative( + return_type='InputStream', + system_class=False, + static=True, + name='openUrl', + method_id_var_name='openUrl', + java_class_name='', + params=[Param(datatype='String', name='url')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='activateHardwareAcceleration', + method_id_var_name='activateHardwareAcceleration', + java_class_name='', + params=[Param(datatype='boolean', name='activated'), + Param(datatype='int', name='iPid'), + Param(datatype='int', name='iType'), + Param(datatype='int', name='iPrimaryID'), + Param(datatype='int', name='iSecondaryID'), + ], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='int', + system_class=False, + static=True, + name='updateStatus', + method_id_var_name='updateStatus', + java_class_name='', + params=[Param(datatype='int', name='status')], + env_call=('Integer', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='uncheckedCall', + method_id_var_name='uncheckedCall', + java_class_name='', + params=[Param(datatype='int', name='iParam')], + env_call=('Void', ''), + unchecked=True, + ), + CalledByNative( + return_type='byte[]', + system_class=False, + static=False, + name='returnByteArray', + method_id_var_name='returnByteArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='boolean[]', + system_class=False, + static=False, + name='returnBooleanArray', + method_id_var_name='returnBooleanArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='char[]', + system_class=False, + static=False, + name='returnCharArray', + method_id_var_name='returnCharArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='short[]', + system_class=False, + static=False, + name='returnShortArray', + method_id_var_name='returnShortArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='int[]', + system_class=False, + static=False, + name='returnIntArray', + method_id_var_name='returnIntArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='long[]', + system_class=False, + static=False, + name='returnLongArray', + method_id_var_name='returnLongArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='double[]', + system_class=False, + static=False, + name='returnDoubleArray', + method_id_var_name='returnDoubleArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='Object[]', + system_class=False, + static=False, + name='returnObjectArray', + method_id_var_name='returnObjectArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='byte[][]', + system_class=False, + static=False, + name='returnArrayOfByteArray', + method_id_var_name='returnArrayOfByteArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='Bitmap.CompressFormat', + system_class=False, + static=False, + name='getCompressFormat', + method_id_var_name='getCompressFormat', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='List', + system_class=False, + static=False, + name='getCompressFormatList', + method_id_var_name='getCompressFormatList', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + ] + self.assertListEquals(golden_called_by_natives, called_by_natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + [], called_by_natives, [], + TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testCalledByNativeParseError(self): + try: + jni_generator.ExtractCalledByNatives(""" +@CalledByNative +public static int foo(); // This one is fine + +@CalledByNative +scooby doo +""") + self.fail('Expected a ParseError') + except jni_generator.ParseError, e: + self.assertEquals(('@CalledByNative', 'scooby doo'), e.context_lines) + + def testFullyQualifiedClassName(self): + contents = """ +// Copyright (c) 2010 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. + +package org.chromium.content.browser; + +import org.chromium.base.BuildInfo; +""" + self.assertEquals('org/chromium/content/browser/Foo', + jni_generator.ExtractFullyQualifiedJavaClassName( + 'org/chromium/content/browser/Foo.java', contents)) + self.assertEquals('org/chromium/content/browser/Foo', + jni_generator.ExtractFullyQualifiedJavaClassName( + 'frameworks/Foo.java', contents)) + self.assertRaises(SyntaxError, + jni_generator.ExtractFullyQualifiedJavaClassName, + 'com/foo/Bar', 'no PACKAGE line') + + def testMethodNameMangling(self): + self.assertEquals('closeV', + jni_generator.GetMangledMethodName('close', [], 'void')) + self.assertEquals('readI_AB_I_I', + jni_generator.GetMangledMethodName('read', + [Param(name='p1', + datatype='byte[]'), + Param(name='p2', + datatype='int'), + Param(name='p3', + datatype='int'),], + 'int')) + self.assertEquals('openJIIS_JLS', + jni_generator.GetMangledMethodName('open', + [Param(name='p1', + datatype='java/lang/String'),], + 'java/io/InputStream')) + + def testFromJavaPGenerics(self): + contents = """ +public abstract class java.util.HashSet extends java.util.AbstractSet + implements java.util.Set, java.lang.Cloneable, java.io.Serializable { + public void dummy(); + Signature: ()V +} +""" + jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'), + TestOptions()) + self.assertEquals(1, len(jni_from_javap.called_by_natives)) + self.assertGoldenTextEquals(jni_from_javap.GetContent()) + + def testSnippnetJavap6_7_8(self): + content_javap6 = """ +public class java.util.HashSet { +public boolean add(java.lang.Object); + Signature: (Ljava/lang/Object;)Z +} +""" + + content_javap7 = """ +public class java.util.HashSet { +public boolean add(E); + Signature: (Ljava/lang/Object;)Z +} +""" + + content_javap8 = """ +public class java.util.HashSet { + public boolean add(E); + descriptor: (Ljava/lang/Object;)Z +} +""" + + jni_from_javap6 = jni_generator.JNIFromJavaP(content_javap6.split('\n'), + TestOptions()) + jni_from_javap7 = jni_generator.JNIFromJavaP(content_javap7.split('\n'), + TestOptions()) + jni_from_javap8 = jni_generator.JNIFromJavaP(content_javap8.split('\n'), + TestOptions()) + self.assertTrue(jni_from_javap6.GetContent()) + self.assertTrue(jni_from_javap7.GetContent()) + self.assertTrue(jni_from_javap8.GetContent()) + # Ensure the javap7 is correctly parsed and uses the Signature field rather + # than the "E" parameter. + self.assertTextEquals(jni_from_javap6.GetContent(), + jni_from_javap7.GetContent()) + # Ensure the javap8 is correctly parsed and uses the descriptor field. + self.assertTextEquals(jni_from_javap7.GetContent(), + jni_from_javap8.GetContent()) + + def testFromJavaP(self): + contents = self._ReadGoldenFile(os.path.join(os.path.dirname(sys.argv[0]), + 'testInputStream.javap')) + jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'), + TestOptions()) + self.assertEquals(10, len(jni_from_javap.called_by_natives)) + self.assertGoldenTextEquals(jni_from_javap.GetContent()) + + def testConstantsFromJavaP(self): + for f in ['testMotionEvent.javap', 'testMotionEvent.javap7']: + contents = self._ReadGoldenFile(os.path.join(os.path.dirname(sys.argv[0]), + f)) + jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'), + TestOptions()) + self.assertEquals(86, len(jni_from_javap.called_by_natives)) + self.assertGoldenTextEquals(jni_from_javap.GetContent()) + + def testREForNatives(self): + # We should not match "native SyncSetupFlow" inside the comment. + test_data = """ + /** + * Invoked when the setup process is complete so we can disconnect from the + * native-side SyncSetupFlowHandler. + */ + public void destroy() { + Log.v(TAG, "Destroying native SyncSetupFlow"); + if (mNativeSyncSetupFlow != 0) { + nativeSyncSetupEnded(mNativeSyncSetupFlow); + mNativeSyncSetupFlow = 0; + } + } + private native void nativeSyncSetupEnded( + int nativeAndroidSyncSetupFlowHandler); + """ + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'foo/bar', TestOptions()) + + def testRaisesOnNonJNIMethod(self): + test_data = """ + class MyInnerClass { + private int Foo(int p0) { + } + } + """ + self.assertRaises(SyntaxError, + jni_generator.JNIFromJavaSource, + test_data, 'foo/bar', TestOptions()) + + def testJniSelfDocumentingExample(self): + script_dir = os.path.dirname(sys.argv[0]) + content = file(os.path.join(script_dir, + 'java/src/org/chromium/example/jni_generator/SampleForTests.java') + ).read() + golden_file = os.path.join(script_dir, 'SampleForTests_jni.golden') + golden_content = file(golden_file).read() + jni_from_java = jni_generator.JNIFromJavaSource( + content, 'org/chromium/example/jni_generator/SampleForTests', + TestOptions()) + generated_text = jni_from_java.GetContent() + if not self.compareText(golden_content, generated_text): + if os.environ.get(REBASELINE_ENV): + with file(golden_file, 'w') as f: + f.write(generated_text) + return + self.fail('testJniSelfDocumentingExample') + + def testNoWrappingPreprocessorLines(self): + test_data = """ + package com.google.lookhowextremelylongiam.snarf.icankeepthisupallday; + + class ReallyLongClassNamesAreAllTheRage { + private static native int nativeTest(); + } + """ + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, ('com/google/lookhowextremelylongiam/snarf/' + 'icankeepthisupallday/ReallyLongClassNamesAreAllTheRage'), + TestOptions()) + jni_lines = jni_from_java.GetContent().split('\n') + line = filter(lambda line: line.lstrip().startswith('#ifndef'), + jni_lines)[0] + self.assertTrue(len(line) > 80, + ('Expected #ifndef line to be > 80 chars: ', line)) + + def testImports(self): + import_header = """ +// Copyright (c) 2012 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. + +package org.chromium.content.app; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.SurfaceTexture; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; +import android.view.Surface; + +import java.util.ArrayList; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.content.app.ContentMain; +import org.chromium.content.browser.SandboxedProcessConnection; +import org.chromium.content.common.ISandboxedProcessCallback; +import org.chromium.content.common.ISandboxedProcessService; +import org.chromium.content.common.WillNotRaise.AnException; +import org.chromium.content.common.WillRaise.AnException; + +import static org.chromium.Bar.Zoo; + +class Foo { + public static class BookmarkNode implements Parcelable { + } + public interface PasswordListObserver { + } +} + """ + jni_generator.JniParams.SetFullyQualifiedClass( + 'org/chromium/content/app/Foo') + jni_generator.JniParams.ExtractImportsAndInnerClasses(import_header) + self.assertTrue('Lorg/chromium/content/common/ISandboxedProcessService' in + jni_generator.JniParams._imports) + self.assertTrue('Lorg/chromium/Bar/Zoo' in + jni_generator.JniParams._imports) + self.assertTrue('Lorg/chromium/content/app/Foo$BookmarkNode' in + jni_generator.JniParams._inner_classes) + self.assertTrue('Lorg/chromium/content/app/Foo$PasswordListObserver' in + jni_generator.JniParams._inner_classes) + self.assertEquals('Lorg/chromium/content/app/ContentMain$Inner;', + jni_generator.JniParams.JavaToJni('ContentMain.Inner')) + self.assertRaises(SyntaxError, + jni_generator.JniParams.JavaToJni, + 'AnException') + + def testJniParamsJavaToJni(self): + self.assertTextEquals('I', JniParams.JavaToJni('int')) + self.assertTextEquals('[B', JniParams.JavaToJni('byte[]')) + self.assertTextEquals( + '[Ljava/nio/ByteBuffer;', JniParams.JavaToJni('java/nio/ByteBuffer[]')) + + def testNativesLong(self): + test_options = TestOptions() + test_options.ptr_type = 'long' + test_data = """" + private native void nativeDestroy(long nativeChromeBrowserProvider); + """ + jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data) + natives = jni_generator.ExtractNatives(test_data, test_options.ptr_type) + golden_natives = [ + NativeMethod(return_type='void', static=False, name='Destroy', + params=[Param(datatype='long', + name='nativeChromeBrowserProvider')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider', + ptr_type=test_options.ptr_type), + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], test_options) + self.assertGoldenTextEquals(h.GetContent()) + + def testMainDexFile(self): + test_data = """ + package org.chromium.example.jni_generator; + + @MainDex + class Test { + private static native int nativeStaticMethod(long nativeTest, int arg1); + } + """ + options = TestOptions() + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'org/chromium/foo/Bar', options) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + def testNonMainDexFile(self): + test_data = """ + package org.chromium.example.jni_generator; + + class Test { + private static native int nativeStaticMethod(long nativeTest, int arg1); + } + """ + options = TestOptions() + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'org/chromium/foo/Bar', options) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + def testNativeExportsOnlyOption(self): + test_data = """ + package org.chromium.example.jni_generator; + + /** The pointer to the native Test. */ + long nativeTest; + + class Test { + private static native int nativeStaticMethod(long nativeTest, int arg1); + private native int nativeMethod(long nativeTest, int arg1); + @CalledByNative + private void testMethodWithParam(int iParam); + @CalledByNative + private String testMethodWithParamAndReturn(int iParam); + @CalledByNative + private static int testStaticMethodWithParam(int iParam); + @CalledByNative + private static double testMethodWithNoParam(); + @CalledByNative + private static String testStaticMethodWithNoParam(); + + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + } + """ + options = TestOptions() + options.native_exports_optional = False + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'org/chromium/example/jni_generator/SampleForTests', options) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + def testOuterInnerRaises(self): + test_data = """ + package org.chromium.media; + + @CalledByNative + static int getCaptureFormatWidth(VideoCapture.CaptureFormat format) { + return format.getWidth(); + } + """ + def willRaise(): + jni_generator.JNIFromJavaSource( + test_data, + 'org/chromium/media/VideoCaptureFactory', + TestOptions()) + self.assertRaises(SyntaxError, willRaise) + + def testSingleJNIAdditionalImport(self): + test_data = """ + package org.chromium.foo; + + @JNIAdditionalImport(Bar.class) + class Foo { + + @CalledByNative + private static void calledByNative(Bar.Callback callback) { + } + + private static native void nativeDoSomething(Bar.Callback callback); + } + """ + jni_from_java = jni_generator.JNIFromJavaSource(test_data, + 'org/chromium/foo/Foo', + TestOptions()) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + def testMultipleJNIAdditionalImport(self): + test_data = """ + package org.chromium.foo; + + @JNIAdditionalImport({Bar1.class, Bar2.class}) + class Foo { + + @CalledByNative + private static void calledByNative(Bar1.Callback callback1, + Bar2.Callback callback2) { + } + + private static native void nativeDoSomething(Bar1.Callback callback1, + Bar2.Callback callback2); + } + """ + jni_from_java = jni_generator.JNIFromJavaSource(test_data, + 'org/chromium/foo/Foo', + TestOptions()) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + +def TouchStamp(stamp_path): + dir_name = os.path.dirname(stamp_path) + if not os.path.isdir(dir_name): + os.makedirs(dir_name) + + with open(stamp_path, 'a'): + os.utime(stamp_path, None) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--stamp', help='Path to touch on success.') + options, _ = parser.parse_args(argv[1:]) + + test_result = unittest.main(argv=argv[0:1], exit=False) + + if test_result.result.wasSuccessful() and options.stamp: + TouchStamp(options.stamp) + + return not test_result.result.wasSuccessful() + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/base/android/jni_generator/sample_for_tests.cc b/base/android/jni_generator/sample_for_tests.cc new file mode 100644 index 0000000000000000000000000000000000000000..42b2143e9898fb22a0454e07c00a48e5179d11c7 --- /dev/null +++ b/base/android/jni_generator/sample_for_tests.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2012 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. + +#include + +#include "base/android/jni_generator/sample_for_tests.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +// Generated file for JNI bindings from C++ to Java @CalledByNative methods. +// Only to be included in one .cc file. +// Name is based on the java file name: *.java -> jni/*_jni.h +#include "jni/SampleForTests_jni.h" // Generated by JNI. + +using base::android::AttachCurrentThread; +using base::android::ConvertJavaStringToUTF8; +using base::android::ConvertUTF8ToJavaString; +using base::android::ScopedJavaLocalRef; + +namespace base { +namespace android { + +jdouble CPPClass::InnerClass::MethodOtherP0( + JNIEnv* env, + const JavaParamRef& caller) { + return 0.0; +} + +CPPClass::CPPClass() { +} + +CPPClass::~CPPClass() { +} + +// static +bool CPPClass::RegisterJNI(JNIEnv* env) { + return RegisterNativesImpl(env); // Generated in SampleForTests_jni.h +} + +void CPPClass::Destroy(JNIEnv* env, const JavaParamRef& caller) { + delete this; +} + +jint CPPClass::Method(JNIEnv* env, const JavaParamRef& caller) { + return 0; +} + +void CPPClass::AddStructB(JNIEnv* env, + const JavaParamRef& caller, + const JavaParamRef& structb) { + long key = Java_InnerStructB_getKey(env, structb); + std::string value = + ConvertJavaStringToUTF8(env, Java_InnerStructB_getValue(env, structb)); + map_[key] = value; +} + +void CPPClass::IterateAndDoSomethingWithStructB( + JNIEnv* env, + const JavaParamRef& caller) { + // Iterate over the elements and do something with them. + for (std::map::const_iterator it = map_.begin(); + it != map_.end(); ++it) { + long key = it->first; + std::string value = it->second; + std::cout << key << value; + } + map_.clear(); +} + +ScopedJavaLocalRef CPPClass::ReturnAString( + JNIEnv* env, + const JavaParamRef& caller) { + return ConvertUTF8ToJavaString(env, "test"); +} + +// Static free functions declared and called directly from java. +static jlong Init(JNIEnv* env, + const JavaParamRef& caller, + const JavaParamRef& param) { + return 0; +} + +static jdouble GetDoubleFunction(JNIEnv*, const JavaParamRef&) { + return 0; +} + +static jfloat GetFloatFunction(JNIEnv*, const JavaParamRef&) { + return 0; +} + +static void SetNonPODDatatype(JNIEnv*, + const JavaParamRef&, + const JavaParamRef&) {} + +static ScopedJavaLocalRef GetNonPODDatatype( + JNIEnv*, + const JavaParamRef&) { + return ScopedJavaLocalRef(); +} + +static jint GetInnerIntFunction(JNIEnv*, const JavaParamRef&) { + return 0; +} + +} // namespace android +} // namespace base + +int main() { + // On a regular application, you'd call AttachCurrentThread(). This sample is + // not yet linking with all the libraries. + JNIEnv* env = /* AttachCurrentThread() */ NULL; + + // This is how you call a java static method from C++. + bool foo = base::android::Java_SampleForTests_staticJavaMethod(env); + + // This is how you call a java method from C++. Note that you must have + // obtained the jobject somehow. + ScopedJavaLocalRef my_java_object; + int bar = base::android::Java_SampleForTests_javaMethod( + env, my_java_object, 1, 2); + + std::cout << foo << bar; + + for (int i = 0; i < 10; ++i) { + // Creates a "struct" that will then be used by the java side. + ScopedJavaLocalRef struct_a = + base::android::Java_InnerStructA_create( + env, 0, 1, ConvertUTF8ToJavaString(env, "test")); + base::android::Java_SampleForTests_addStructA(env, my_java_object, + struct_a); + } + base::android::Java_SampleForTests_iterateAndDoSomething(env, my_java_object); + base::android::Java_SampleForTests_packagePrivateJavaMethod(env, + my_java_object); + base::android::Java_SampleForTests_methodThatThrowsException(env, + my_java_object); + base::android::Java_SampleForTests_javaMethodWithAnnotatedParam( + env, my_java_object, 42); + return 0; +} diff --git a/base/android/jni_generator/sample_for_tests.h b/base/android/jni_generator/sample_for_tests.h new file mode 100644 index 0000000000000000000000000000000000000000..a9cf7b05e9f9dc824e52df16511dc3e2cca1a44d --- /dev/null +++ b/base/android/jni_generator/sample_for_tests.h @@ -0,0 +1,126 @@ +// Copyright (c) 2013 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 BASE_ANDROID_JNI_GENERATOR_SAMPLE_FOR_TESTS_H_ +#define BASE_ANDROID_JNI_GENERATOR_SAMPLE_FOR_TESTS_H_ + +#include +#include +#include + +#include "base/android/jni_android.h" + +namespace base { +namespace android { + +// This file is used to: +// - document the best practices and guidelines on JNI usage. +// - ensure sample_for_tests_jni.h compiles and the functions declared in it +// as expected. +// +// Methods are called directly from Java (except RegisterJNI). More +// documentation in SampleForTests.java +// +// For C++ to access Java methods: +// - GN Build must be configured to generate bindings: +// # Add import at top of file: +// if (is_android) { +// import("//build/config/android/rules.gni") # For generate_jni(). +// } +// # ... +// # An example target that will rely on JNI: +// component("foo") { +// # ... normal sources, defines, deps. +// # For each jni generated .java -> .h header file in jni_headers +// # target there will be a single .cc file here that includes it. +// # +// # Add a dep for JNI: +// if (is_android) { +// deps += [ ":foo_jni" ] +// } +// } +// # ... +// # Create target for JNI: +// if (is_android) { +// generate_jni("jni_headers") { +// sources = [ +// "java/src/org/chromium/example/jni_generator/SampleForTests.java", +// ] +// jni_package = "foo" +// } +// android_library("java") { +// java_files = [ +// "java/src/org/chromium/example/jni_generator/SampleForTests.java", +// "java/src/org/chromium/example/jni_generator/NonJniFile.java", +// ] +// } +// } +// +// For C++ methods to be exposed to Java: +// - The generated RegisterNativesImpl method must be called, this is typically +// done by having a static RegisterJNI method in the C++ class. +// - The RegisterJNI method is added to a module's collection of register +// methods, such as: example_jni_registrar.h/cc files which call +// base::android::RegisterNativeMethods. +// An example_jni_registstrar.cc: +// +// namespace { +// const base::android::RegistrationMethod kRegisteredMethods[] = { +// // Initial string is for debugging only. +// { "ExampleName", base::ExampleNameAndroid::RegisterJNI }, +// { "ExampleName2", base::ExampleName2Android::RegisterJNI }, +// }; +// } // namespace +// +// bool RegisterModuleNameJni(JNIEnv* env) { +// return RegisterNativeMethods(env, kRegisteredMethods, +// arraysize(kRegisteredMethods)); +// } +// +// - Each module's RegisterModuleNameJni must be called by a larger module, +// or application during startup. +// +class CPPClass { + public: + CPPClass(); + ~CPPClass(); + + // Register C++ methods exposed to Java using JNI. + static bool RegisterJNI(JNIEnv* env); + + // Java @CalledByNative methods implicitly available to C++ via the _jni.h + // file included in the .cc file. + + class InnerClass { + public: + jdouble MethodOtherP0(JNIEnv* env, + const base::android::JavaParamRef& caller); + }; + + void Destroy(JNIEnv* env, const base::android::JavaParamRef& caller); + + jint Method(JNIEnv* env, const base::android::JavaParamRef& caller); + + void AddStructB(JNIEnv* env, + const base::android::JavaParamRef& caller, + const base::android::JavaParamRef& structb); + + void IterateAndDoSomethingWithStructB( + JNIEnv* env, + const base::android::JavaParamRef& caller); + + base::android::ScopedJavaLocalRef ReturnAString( + JNIEnv* env, + const base::android::JavaParamRef& caller); + + private: + std::map map_; + + DISALLOW_COPY_AND_ASSIGN(CPPClass); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_GENERATOR_SAMPLE_FOR_TESTS_H_ diff --git a/base/android/jni_generator/testCalledByNatives.golden b/base/android/jni_generator/testCalledByNatives.golden new file mode 100644 index 0000000000000000000000000000000000000000..ac86b2e801506470927d012b806a69ddd24d8133 --- /dev/null +++ b/base/android/jni_generator/testCalledByNatives.golden @@ -0,0 +1,497 @@ +// Copyright 2014 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kInfoBarClassPath[] = "org/chromium/TestJni$InfoBar"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_InfoBar_clazz __attribute__((unused)) = 0; +#define InfoBar_clazz(env) base::android::LazyGetClass(env, kInfoBarClassPath, &g_InfoBar_clazz) + +} // namespace + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_TestJni_showConfirmInfoBar = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_showConfirmInfoBar(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper nativeInfoBar, + const base::android::JavaRefOrBare& buttonOk, + const base::android::JavaRefOrBare& buttonCancel, + const base::android::JavaRefOrBare& title, + const base::android::JavaRefOrBare& icon) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "showConfirmInfoBar", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Landroid/graphics/Bitmap;" +")" +"Lorg/chromium/Foo$InnerClass;", + &g_TestJni_showConfirmInfoBar); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id, as_jint(nativeInfoBar), buttonOk.obj(), buttonCancel.obj(), + title.obj(), icon.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_showAutoLoginInfoBar = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_showAutoLoginInfoBar(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper nativeInfoBar, + const base::android::JavaRefOrBare& realm, + const base::android::JavaRefOrBare& account, + const base::android::JavaRefOrBare& args) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "showAutoLoginInfoBar", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Lorg/chromium/Foo$InnerClass;", + &g_TestJni_showAutoLoginInfoBar); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id, as_jint(nativeInfoBar), realm.obj(), account.obj(), + args.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_InfoBar_dismiss = 0; +static void Java_InfoBar_dismiss(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InfoBar_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InfoBar_clazz(env), + "dismiss", +"(" +")" +"V", + &g_InfoBar_dismiss); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_TestJni_shouldShowAutoLogin = 0; +static jboolean Java_TestJni_shouldShowAutoLogin(JNIEnv* env, const + base::android::JavaRefOrBare& view, + const base::android::JavaRefOrBare& realm, + const base::android::JavaRefOrBare& account, + const base::android::JavaRefOrBare& args) { + CHECK_CLAZZ(env, TestJni_clazz(env), + TestJni_clazz(env), false); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, TestJni_clazz(env), + "shouldShowAutoLogin", +"(" +"Landroid/view/View;" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Z", + &g_TestJni_shouldShowAutoLogin); + + jboolean ret = + env->CallStaticBooleanMethod(TestJni_clazz(env), + method_id, view.obj(), realm.obj(), account.obj(), args.obj()); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_TestJni_openUrl = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_openUrl(JNIEnv* + env, const base::android::JavaRefOrBare& url) { + CHECK_CLAZZ(env, TestJni_clazz(env), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, TestJni_clazz(env), + "openUrl", +"(" +"Ljava/lang/String;" +")" +"Ljava/io/InputStream;", + &g_TestJni_openUrl); + + jobject ret = + env->CallStaticObjectMethod(TestJni_clazz(env), + method_id, url.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_activateHardwareAcceleration = 0; +static void Java_TestJni_activateHardwareAcceleration(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jboolean activated, + JniIntWrapper iPid, + JniIntWrapper iType, + JniIntWrapper iPrimaryID, + JniIntWrapper iSecondaryID) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "activateHardwareAcceleration", +"(" +"Z" +"I" +"I" +"I" +"I" +")" +"V", + &g_TestJni_activateHardwareAcceleration); + + env->CallVoidMethod(obj.obj(), + method_id, activated, as_jint(iPid), as_jint(iType), + as_jint(iPrimaryID), as_jint(iSecondaryID)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_TestJni_updateStatus = 0; +static jint Java_TestJni_updateStatus(JNIEnv* env, JniIntWrapper status) { + CHECK_CLAZZ(env, TestJni_clazz(env), + TestJni_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, TestJni_clazz(env), + "updateStatus", +"(" +"I" +")" +"I", + &g_TestJni_updateStatus); + + jint ret = + env->CallStaticIntMethod(TestJni_clazz(env), + method_id, as_jint(status)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_TestJni_uncheckedCall = 0; +static void Java_TestJni_uncheckedCall(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper iParam) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "uncheckedCall", +"(" +"I" +")" +"V", + &g_TestJni_uncheckedCall); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(iParam)); +} + +static base::subtle::AtomicWord g_TestJni_returnByteArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnByteArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnByteArray", +"(" +")" +"[B", + &g_TestJni_returnByteArray); + + jbyteArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnBooleanArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnBooleanArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnBooleanArray", +"(" +")" +"[Z", + &g_TestJni_returnBooleanArray); + + jbooleanArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnCharArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnCharArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnCharArray", +"(" +")" +"[C", + &g_TestJni_returnCharArray); + + jcharArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnShortArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnShortArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnShortArray", +"(" +")" +"[S", + &g_TestJni_returnShortArray); + + jshortArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnIntArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnIntArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnIntArray", +"(" +")" +"[I", + &g_TestJni_returnIntArray); + + jintArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnLongArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnLongArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnLongArray", +"(" +")" +"[J", + &g_TestJni_returnLongArray); + + jlongArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnDoubleArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnDoubleArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnDoubleArray", +"(" +")" +"[D", + &g_TestJni_returnDoubleArray); + + jdoubleArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnObjectArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnObjectArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnObjectArray", +"(" +")" +"[Ljava/lang/Object;", + &g_TestJni_returnObjectArray); + + jobjectArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnArrayOfByteArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnArrayOfByteArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnArrayOfByteArray", +"(" +")" +"[[B", + &g_TestJni_returnArrayOfByteArray); + + jobjectArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_getCompressFormat = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_getCompressFormat(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "getCompressFormat", +"(" +")" +"Landroid/graphics/Bitmap$CompressFormat;", + &g_TestJni_getCompressFormat); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_getCompressFormatList = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_getCompressFormatList(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "getCompressFormatList", +"(" +")" +"Ljava/util/List;", + &g_TestJni_getCompressFormatList); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +// Step 3: RegisterNatives. + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testConstantsFromJavaP.golden b/base/android/jni_generator/testConstantsFromJavaP.golden new file mode 100644 index 0000000000000000000000000000000000000000..b16956f7f55a49a6223af4da99f608bf32679861 --- /dev/null +++ b/base/android/jni_generator/testConstantsFromJavaP.golden @@ -0,0 +1,2195 @@ +// Copyright 2014 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// android/view/MotionEvent + +#ifndef android_view_MotionEvent_JNI +#define android_view_MotionEvent_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kMotionEventClassPath[] = "android/view/MotionEvent"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_MotionEvent_clazz __attribute__((unused)) = 0; +#define MotionEvent_clazz(env) base::android::LazyGetClass(env, kMotionEventClassPath, &g_MotionEvent_clazz) + +} // namespace + +namespace JNI_MotionEvent { + +enum Java_MotionEvent_constant_fields { + INVALID_POINTER_ID = -1, + ACTION_MASK = 255, + ACTION_DOWN = 0, + ACTION_UP = 1, + ACTION_MOVE = 2, + ACTION_CANCEL = 3, + ACTION_OUTSIDE = 4, + ACTION_POINTER_DOWN = 5, + ACTION_POINTER_UP = 6, + ACTION_HOVER_MOVE = 7, + ACTION_SCROLL = 8, + ACTION_HOVER_ENTER = 9, + ACTION_HOVER_EXIT = 10, + ACTION_POINTER_INDEX_MASK = 65280, + ACTION_POINTER_INDEX_SHIFT = 8, + ACTION_POINTER_1_DOWN = 5, + ACTION_POINTER_2_DOWN = 261, + ACTION_POINTER_3_DOWN = 517, + ACTION_POINTER_1_UP = 6, + ACTION_POINTER_2_UP = 262, + ACTION_POINTER_3_UP = 518, + ACTION_POINTER_ID_MASK = 65280, + ACTION_POINTER_ID_SHIFT = 8, + FLAG_WINDOW_IS_OBSCURED = 1, + EDGE_TOP = 1, + EDGE_BOTTOM = 2, + EDGE_LEFT = 4, + EDGE_RIGHT = 8, + AXIS_X = 0, + AXIS_Y = 1, + AXIS_PRESSURE = 2, + AXIS_SIZE = 3, + AXIS_TOUCH_MAJOR = 4, + AXIS_TOUCH_MINOR = 5, + AXIS_TOOL_MAJOR = 6, + AXIS_TOOL_MINOR = 7, + AXIS_ORIENTATION = 8, + AXIS_VSCROLL = 9, + AXIS_HSCROLL = 10, + AXIS_Z = 11, + AXIS_RX = 12, + AXIS_RY = 13, + AXIS_RZ = 14, + AXIS_HAT_X = 15, + AXIS_HAT_Y = 16, + AXIS_LTRIGGER = 17, + AXIS_RTRIGGER = 18, + AXIS_THROTTLE = 19, + AXIS_RUDDER = 20, + AXIS_WHEEL = 21, + AXIS_GAS = 22, + AXIS_BRAKE = 23, + AXIS_DISTANCE = 24, + AXIS_TILT = 25, + AXIS_GENERIC_1 = 32, + AXIS_GENERIC_2 = 33, + AXIS_GENERIC_3 = 34, + AXIS_GENERIC_4 = 35, + AXIS_GENERIC_5 = 36, + AXIS_GENERIC_6 = 37, + AXIS_GENERIC_7 = 38, + AXIS_GENERIC_8 = 39, + AXIS_GENERIC_9 = 40, + AXIS_GENERIC_10 = 41, + AXIS_GENERIC_11 = 42, + AXIS_GENERIC_12 = 43, + AXIS_GENERIC_13 = 44, + AXIS_GENERIC_14 = 45, + AXIS_GENERIC_15 = 46, + AXIS_GENERIC_16 = 47, + BUTTON_PRIMARY = 1, + BUTTON_SECONDARY = 2, + BUTTON_TERTIARY = 4, + BUTTON_BACK = 8, + BUTTON_FORWARD = 16, + TOOL_TYPE_UNKNOWN = 0, + TOOL_TYPE_FINGER = 1, + TOOL_TYPE_STYLUS = 2, + TOOL_TYPE_MOUSE = 3, + TOOL_TYPE_ERASER = 4, +}; + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_MotionEvent_finalize = 0; +static void Java_MotionEvent_finalize(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static void Java_MotionEvent_finalize(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "finalize", + "()V", + &g_MotionEvent_finalize); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord + g_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(JNIEnv* + env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRefOrBare& p4, + const base::android::JavaRefOrBare& p5, + JniIntWrapper p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12, + JniIntWrapper p13) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(JNIEnv* + env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRefOrBare& p4, + const base::android::JavaRefOrBare& p5, + JniIntWrapper p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12, + JniIntWrapper p13) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", +"(JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), as_jint(p3), p4.obj(), p5.obj(), + as_jint(p6), as_jint(p7), p8, p9, as_jint(p10), as_jint(p11), + as_jint(p12), as_jint(p13)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord + g_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(JNIEnv* env, + jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRefOrBare& p4, + const base::android::JavaRefOrBare& p5, + JniIntWrapper p6, + jfloat p7, + jfloat p8, + JniIntWrapper p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(JNIEnv* env, + jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRefOrBare& p4, + const base::android::JavaRefOrBare& p5, + JniIntWrapper p6, + jfloat p7, + jfloat p8, + JniIntWrapper p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", +"(JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), as_jint(p3), p4.obj(), p5.obj(), + as_jint(p6), p7, p8, as_jint(p9), as_jint(p10), as_jint(p11), + as_jint(p12)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I + = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + jfloat p5, + jfloat p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + jfloat p5, + jfloat p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", + "(JJIFFFFIFFII)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), p3, p4, p5, p6, as_jint(p7), p8, p9, + as_jint(p10), as_jint(p11)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord + g_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + jfloat p4, + jfloat p5, + jfloat p6, + jfloat p7, + JniIntWrapper p8, + jfloat p9, + jfloat p10, + JniIntWrapper p11, + JniIntWrapper p12) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + jfloat p4, + jfloat p5, + jfloat p6, + jfloat p7, + JniIntWrapper p8, + jfloat p9, + jfloat p10, + JniIntWrapper p11, + JniIntWrapper p12) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", + "(JJIIFFFFIFFII)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), as_jint(p3), p4, p5, p6, p7, + as_jint(p8), p9, p10, as_jint(p11), as_jint(p12)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_obtainAVME_J_J_I_F_F_I = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", + "(JJIFFI)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_J_J_I_F_F_I); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), p3, p4, as_jint(p5)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_obtainAVME_AVME = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, const + base::android::JavaRefOrBare& p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, const + base::android::JavaRefOrBare& p0) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", + "(Landroid/view/MotionEvent;)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_AVME); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_obtainNoHistory = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainNoHistory(JNIEnv* env, const + base::android::JavaRefOrBare& p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainNoHistory(JNIEnv* env, const + base::android::JavaRefOrBare& p0) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtainNoHistory", + "(Landroid/view/MotionEvent;)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainNoHistory); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_recycle = 0; +static void Java_MotionEvent_recycle(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static void Java_MotionEvent_recycle(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "recycle", + "()V", + &g_MotionEvent_recycle); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_getDeviceId = 0; +static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getDeviceId", + "()I", + &g_MotionEvent_getDeviceId); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getSource = 0; +static jint Java_MotionEvent_getSource(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getSource(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getSource", + "()I", + &g_MotionEvent_getSource); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_setSource = 0; +static void Java_MotionEvent_setSource(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static void Java_MotionEvent_setSource(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "setSource", + "(I)V", + &g_MotionEvent_setSource); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_getAction = 0; +static jint Java_MotionEvent_getAction(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getAction(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getAction", + "()I", + &g_MotionEvent_getAction); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getActionMasked = 0; +static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getActionMasked", + "()I", + &g_MotionEvent_getActionMasked); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getActionIndex = 0; +static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getActionIndex", + "()I", + &g_MotionEvent_getActionIndex); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getFlags = 0; +static jint Java_MotionEvent_getFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getFlags", + "()I", + &g_MotionEvent_getFlags); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getDownTime = 0; +static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getDownTime", + "()J", + &g_MotionEvent_getDownTime); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getEventTime = 0; +static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getEventTime", + "()J", + &g_MotionEvent_getEventTime); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getXF = 0; +static jfloat Java_MotionEvent_getXF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getXF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getX", + "()F", + &g_MotionEvent_getXF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getYF = 0; +static jfloat Java_MotionEvent_getYF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getYF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getY", + "()F", + &g_MotionEvent_getYF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getPressureF = 0; +static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPressure", + "()F", + &g_MotionEvent_getPressureF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getSizeF = 0; +static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getSize", + "()F", + &g_MotionEvent_getSizeF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getTouchMajorF = 0; +static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getTouchMajor", + "()F", + &g_MotionEvent_getTouchMajorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getTouchMinorF = 0; +static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getTouchMinor", + "()F", + &g_MotionEvent_getTouchMinorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getToolMajorF = 0; +static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getToolMajor", + "()F", + &g_MotionEvent_getToolMajorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getToolMinorF = 0; +static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getToolMinor", + "()F", + &g_MotionEvent_getToolMinorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getOrientationF = 0; +static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getOrientation", + "()F", + &g_MotionEvent_getOrientationF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getAxisValueF_I = 0; +static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getAxisValue", + "(I)F", + &g_MotionEvent_getAxisValueF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getPointerCount = 0; +static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPointerCount", + "()I", + &g_MotionEvent_getPointerCount); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getPointerId = 0; +static jint Java_MotionEvent_getPointerId(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jint Java_MotionEvent_getPointerId(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPointerId", + "(I)I", + &g_MotionEvent_getPointerId); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getToolType = 0; +static jint Java_MotionEvent_getToolType(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jint Java_MotionEvent_getToolType(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getToolType", + "(I)I", + &g_MotionEvent_getToolType); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_findPointerIndex = 0; +static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "findPointerIndex", + "(I)I", + &g_MotionEvent_findPointerIndex); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getXF_I = 0; +static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getX", + "(I)F", + &g_MotionEvent_getXF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getYF_I = 0; +static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getY", + "(I)F", + &g_MotionEvent_getYF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getPressureF_I = 0; +static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPressure", + "(I)F", + &g_MotionEvent_getPressureF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getSizeF_I = 0; +static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getSize", + "(I)F", + &g_MotionEvent_getSizeF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getTouchMajorF_I = 0; +static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getTouchMajor", + "(I)F", + &g_MotionEvent_getTouchMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getTouchMinorF_I = 0; +static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getTouchMinor", + "(I)F", + &g_MotionEvent_getTouchMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getToolMajorF_I = 0; +static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getToolMajor", + "(I)F", + &g_MotionEvent_getToolMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getToolMinorF_I = 0; +static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getToolMinor", + "(I)F", + &g_MotionEvent_getToolMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getOrientationF_I = 0; +static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getOrientation", + "(I)F", + &g_MotionEvent_getOrientationF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getAxisValueF_I_I = 0; +static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getAxisValue", + "(II)F", + &g_MotionEvent_getAxisValueF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getPointerCoords = 0; +static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + const base::android::JavaRefOrBare& p1) __attribute__ ((unused)); +static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + const base::android::JavaRefOrBare& p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPointerCoords", + "(ILandroid/view/MotionEvent$PointerCoords;)V", + &g_MotionEvent_getPointerCoords); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0), p1.obj()); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_getPointerProperties = 0; +static void Java_MotionEvent_getPointerProperties(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + const base::android::JavaRefOrBare& p1) __attribute__ ((unused)); +static void Java_MotionEvent_getPointerProperties(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + const base::android::JavaRefOrBare& p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPointerProperties", + "(ILandroid/view/MotionEvent$PointerProperties;)V", + &g_MotionEvent_getPointerProperties); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0), p1.obj()); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_getMetaState = 0; +static jint Java_MotionEvent_getMetaState(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getMetaState(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getMetaState", + "()I", + &g_MotionEvent_getMetaState); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getButtonState = 0; +static jint Java_MotionEvent_getButtonState(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getButtonState(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getButtonState", + "()I", + &g_MotionEvent_getButtonState); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getRawX = 0; +static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getRawX", + "()F", + &g_MotionEvent_getRawX); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getRawY = 0; +static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getRawY", + "()F", + &g_MotionEvent_getRawY); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getXPrecision = 0; +static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getXPrecision", + "()F", + &g_MotionEvent_getXPrecision); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getYPrecision = 0; +static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getYPrecision", + "()F", + &g_MotionEvent_getYPrecision); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistorySize = 0; +static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistorySize", + "()I", + &g_MotionEvent_getHistorySize); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalEventTime = 0; +static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalEventTime", + "(I)J", + &g_MotionEvent_getHistoricalEventTime); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalXF_I = 0; +static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalX", + "(I)F", + &g_MotionEvent_getHistoricalXF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalYF_I = 0; +static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalY", + "(I)F", + &g_MotionEvent_getHistoricalYF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalPressureF_I = 0; +static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalPressure", + "(I)F", + &g_MotionEvent_getHistoricalPressureF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalSizeF_I = 0; +static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalSize", + "(I)F", + &g_MotionEvent_getHistoricalSizeF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMajorF_I = 0; +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalTouchMajor", + "(I)F", + &g_MotionEvent_getHistoricalTouchMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMinorF_I = 0; +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalTouchMinor", + "(I)F", + &g_MotionEvent_getHistoricalTouchMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMajorF_I = 0; +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalToolMajor", + "(I)F", + &g_MotionEvent_getHistoricalToolMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMinorF_I = 0; +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalToolMinor", + "(I)F", + &g_MotionEvent_getHistoricalToolMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalOrientationF_I = 0; +static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalOrientation", + "(I)F", + &g_MotionEvent_getHistoricalOrientationF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalAxisValueF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalAxisValue", + "(II)F", + &g_MotionEvent_getHistoricalAxisValueF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalXF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalXF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalXF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalX", + "(II)F", + &g_MotionEvent_getHistoricalXF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalYF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalYF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalYF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalY", + "(II)F", + &g_MotionEvent_getHistoricalYF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalPressureF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalPressureF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalPressureF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalPressure", + "(II)F", + &g_MotionEvent_getHistoricalPressureF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalSizeF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalSizeF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalSizeF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalSize", + "(II)F", + &g_MotionEvent_getHistoricalSizeF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMajorF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalTouchMajor", + "(II)F", + &g_MotionEvent_getHistoricalTouchMajorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMinorF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalTouchMinor", + "(II)F", + &g_MotionEvent_getHistoricalTouchMinorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMajorF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalToolMajor", + "(II)F", + &g_MotionEvent_getHistoricalToolMajorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMinorF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalToolMinor", + "(II)F", + &g_MotionEvent_getHistoricalToolMinorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalOrientationF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalOrientationF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalOrientationF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalOrientation", + "(II)F", + &g_MotionEvent_getHistoricalOrientationF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalAxisValueF_I_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1, + JniIntWrapper p2) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1, + JniIntWrapper p2) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalAxisValue", + "(III)F", + &g_MotionEvent_getHistoricalAxisValueF_I_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1), as_jint(p2)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalPointerCoords = 0; +static void Java_MotionEvent_getHistoricalPointerCoords(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1, + const base::android::JavaRefOrBare& p2) __attribute__ ((unused)); +static void Java_MotionEvent_getHistoricalPointerCoords(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1, + const base::android::JavaRefOrBare& p2) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalPointerCoords", + "(IILandroid/view/MotionEvent$PointerCoords;)V", + &g_MotionEvent_getHistoricalPointerCoords); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1), p2.obj()); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_getEdgeFlags = 0; +static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getEdgeFlags", + "()I", + &g_MotionEvent_getEdgeFlags); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_setEdgeFlags = 0; +static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "setEdgeFlags", + "(I)V", + &g_MotionEvent_setEdgeFlags); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_setAction = 0; +static void Java_MotionEvent_setAction(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static void Java_MotionEvent_setAction(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "setAction", + "(I)V", + &g_MotionEvent_setAction); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_offsetLocation = 0; +static void Java_MotionEvent_offsetLocation(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jfloat p0, + jfloat p1) __attribute__ ((unused)); +static void Java_MotionEvent_offsetLocation(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jfloat p0, + jfloat p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "offsetLocation", + "(FF)V", + &g_MotionEvent_offsetLocation); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_setLocation = 0; +static void Java_MotionEvent_setLocation(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jfloat p0, + jfloat p1) __attribute__ ((unused)); +static void Java_MotionEvent_setLocation(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jfloat p0, + jfloat p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "setLocation", + "(FF)V", + &g_MotionEvent_setLocation); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_transform = 0; +static void Java_MotionEvent_transform(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0) __attribute__ ((unused)); +static void Java_MotionEvent_transform(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "transform", + "(Landroid/graphics/Matrix;)V", + &g_MotionEvent_transform); + + env->CallVoidMethod(obj.obj(), + method_id, p0.obj()); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_addBatchV_J_F_F_F_F_I = 0; +static void Java_MotionEvent_addBatchV_J_F_F_F_F_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0, + jfloat p1, + jfloat p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) __attribute__ ((unused)); +static void Java_MotionEvent_addBatchV_J_F_F_F_F_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0, + jfloat p1, + jfloat p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "addBatch", + "(JFFFFI)V", + &g_MotionEvent_addBatchV_J_F_F_F_F_I); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1, p2, p3, p4, as_jint(p5)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_addBatchV_J_LAVMEPC_I = 0; +static void Java_MotionEvent_addBatchV_J_LAVMEPC_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0, + const base::android::JavaRefOrBare& p1, + JniIntWrapper p2) __attribute__ ((unused)); +static void Java_MotionEvent_addBatchV_J_LAVMEPC_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0, + const base::android::JavaRefOrBare& p1, + JniIntWrapper p2) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "addBatch", + "(J[Landroid/view/MotionEvent$PointerCoords;I)V", + &g_MotionEvent_addBatchV_J_LAVMEPC_I); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1.obj(), as_jint(p2)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_toString = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_toString(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_toString(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "toString", + "()Ljava/lang/String;", + &g_MotionEvent_toString); + + jstring ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_actionToString = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_actionToString(JNIEnv* env, JniIntWrapper p0) __attribute__ + ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_actionToString(JNIEnv* env, JniIntWrapper p0) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "actionToString", + "(I)Ljava/lang/String;", + &g_MotionEvent_actionToString); + + jstring ret = + static_cast(env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, as_jint(p0))); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_axisToString = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_axisToString(JNIEnv* env, JniIntWrapper p0) __attribute__ + ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_axisToString(JNIEnv* env, JniIntWrapper p0) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "axisToString", + "(I)Ljava/lang/String;", + &g_MotionEvent_axisToString); + + jstring ret = + static_cast(env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, as_jint(p0))); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_axisFromString = 0; +static jint Java_MotionEvent_axisFromString(JNIEnv* env, const + base::android::JavaRefOrBare& p0) __attribute__ ((unused)); +static jint Java_MotionEvent_axisFromString(JNIEnv* env, const + base::android::JavaRefOrBare& p0) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "axisFromString", + "(Ljava/lang/String;)I", + &g_MotionEvent_axisFromString); + + jint ret = + env->CallStaticIntMethod(MotionEvent_clazz(env), + method_id, p0.obj()); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_writeToParcel = 0; +static void Java_MotionEvent_writeToParcel(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0, + JniIntWrapper p1) __attribute__ ((unused)); +static void Java_MotionEvent_writeToParcel(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "writeToParcel", + "(Landroid/os/Parcel;I)V", + &g_MotionEvent_writeToParcel); + + env->CallVoidMethod(obj.obj(), + method_id, p0.obj(), as_jint(p1)); + jni_generator::CheckException(env); +} + +// Step 3: RegisterNatives. + +} // namespace JNI_MotionEvent + +#endif // android_view_MotionEvent_JNI diff --git a/base/android/jni_generator/testFromJavaP.golden b/base/android/jni_generator/testFromJavaP.golden new file mode 100644 index 0000000000000000000000000000000000000000..18a9430fef25ff60ae7769a8af5abfc5e3ad46ea --- /dev/null +++ b/base/android/jni_generator/testFromJavaP.golden @@ -0,0 +1,260 @@ +// Copyright 2014 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// java/io/InputStream + +#ifndef java_io_InputStream_JNI +#define java_io_InputStream_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kInputStreamClassPath[] = "java/io/InputStream"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_InputStream_clazz __attribute__((unused)) = 0; +#define InputStream_clazz(env) base::android::LazyGetClass(env, kInputStreamClassPath, &g_InputStream_clazz) + +} // namespace + +namespace JNI_InputStream { + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_InputStream_available = 0; +static jint Java_InputStream_available(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_InputStream_available(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "available", + "()I", + &g_InputStream_available); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_close = 0; +static void Java_InputStream_close(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static void Java_InputStream_close(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "close", + "()V", + &g_InputStream_close); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_InputStream_mark = 0; +static void Java_InputStream_mark(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static void Java_InputStream_mark(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "mark", + "(I)V", + &g_InputStream_mark); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_InputStream_markSupported = 0; +static jboolean Java_InputStream_markSupported(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jboolean Java_InputStream_markSupported(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), false); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "markSupported", + "()Z", + &g_InputStream_markSupported); + + jboolean ret = + env->CallBooleanMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI = 0; +static jint Java_InputStream_readI(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_InputStream_readI(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "read", + "()I", + &g_InputStream_readI); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI_AB = 0; +static jint Java_InputStream_readI_AB(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0) __attribute__ ((unused)); +static jint Java_InputStream_readI_AB(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "read", + "([B)I", + &g_InputStream_readI_AB); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, p0.obj()); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI_AB_I_I = 0; +static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0, + JniIntWrapper p1, + JniIntWrapper p2) __attribute__ ((unused)); +static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0, + JniIntWrapper p1, + JniIntWrapper p2) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "read", + "([BII)I", + &g_InputStream_readI_AB_I_I); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, p0.obj(), as_jint(p1), as_jint(p2)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_reset = 0; +static void Java_InputStream_reset(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static void Java_InputStream_reset(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "reset", + "()V", + &g_InputStream_reset); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_InputStream_skip = 0; +static jlong Java_InputStream_skip(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0) __attribute__ + ((unused)); +static jlong Java_InputStream_skip(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "skip", + "(J)J", + &g_InputStream_skip); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id, p0); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_Constructor = 0; +static base::android::ScopedJavaLocalRef + Java_InputStream_Constructor(JNIEnv* env) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_InputStream_Constructor(JNIEnv* env) { + CHECK_CLAZZ(env, InputStream_clazz(env), + InputStream_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "", + "()V", + &g_InputStream_Constructor); + + jobject ret = + env->NewObject(InputStream_clazz(env), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +// Step 3: RegisterNatives. + +} // namespace JNI_InputStream + +#endif // java_io_InputStream_JNI diff --git a/base/android/jni_generator/testFromJavaPGenerics.golden b/base/android/jni_generator/testFromJavaPGenerics.golden new file mode 100644 index 0000000000000000000000000000000000000000..c076c39c369ad4e66a882e2ea7cb498c85378cd4 --- /dev/null +++ b/base/android/jni_generator/testFromJavaPGenerics.golden @@ -0,0 +1,56 @@ +// Copyright 2014 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// java/util/HashSet + +#ifndef java_util_HashSet_JNI +#define java_util_HashSet_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kHashSetClassPath[] = "java/util/HashSet"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_HashSet_clazz __attribute__((unused)) = 0; +#define HashSet_clazz(env) base::android::LazyGetClass(env, kHashSetClassPath, &g_HashSet_clazz) + +} // namespace + +namespace JNI_HashSet { + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_HashSet_dummy = 0; +static void Java_HashSet_dummy(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static void Java_HashSet_dummy(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + HashSet_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, HashSet_clazz(env), + "dummy", + "()V", + &g_HashSet_dummy); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +// Step 3: RegisterNatives. + +} // namespace JNI_HashSet + +#endif // java_util_HashSet_JNI diff --git a/base/android/jni_generator/testInnerClassNatives.golden b/base/android/jni_generator/testInnerClassNatives.golden new file mode 100644 index 0000000000000000000000000000000000000000..20b8830301a8bfa1c595721ab00a76a1e3684509 --- /dev/null +++ b/base/android/jni_generator/testInnerClassNatives.golden @@ -0,0 +1,71 @@ +// Copyright 2014 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kMyInnerClassClassPath[] = "org/chromium/TestJni$MyInnerClass"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_MyInnerClass_clazz __attribute__((unused)) = 0; +#define MyInnerClass_clazz(env) base::android::LazyGetClass(env, kMyInnerClassClassPath, &g_MyInnerClass_clazz) + +} // namespace + +// Step 2: method stubs. + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint + Java_org_chromium_TestJni_00024MyInnerClass_nativeInit(JNIEnv* env, jobject + jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsMyInnerClass[] = { + { "nativeInit", +"(" +")" +"I", + reinterpret_cast(Java_org_chromium_TestJni_00024MyInnerClass_nativeInit) + }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsMyInnerClassSize = arraysize(kMethodsMyInnerClass); + + if (env->RegisterNatives(MyInnerClass_clazz(env), + kMethodsMyInnerClass, + kMethodsMyInnerClassSize) < 0) { + jni_generator::HandleRegistrationError( + env, MyInnerClass_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden new file mode 100644 index 0000000000000000000000000000000000000000..67352e71c407ea29ef9a4f703934047987989a41 --- /dev/null +++ b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden @@ -0,0 +1,98 @@ +// Copyright 2014 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kMyOtherInnerClassClassPath[] = + "org/chromium/TestJni$MyOtherInnerClass"; +const char kTestJniClassPath[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_MyOtherInnerClass_clazz __attribute__((unused)) = 0; +#define MyOtherInnerClass_clazz(env) base::android::LazyGetClass(env, kMyOtherInnerClassClassPath, &g_MyOtherInnerClass_clazz) +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) + +} // namespace + +// Step 2: method stubs. + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit(JNIEnv* env, + jobject jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint + Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit(JNIEnv* env, + jobject jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsMyOtherInnerClass[] = { + { "nativeInit", +"(" +")" +"I", + reinterpret_cast(Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit) + }, +}; + +static const JNINativeMethod kMethodsTestJni[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast(Java_org_chromium_TestJni_nativeInit) }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsMyOtherInnerClassSize = + arraysize(kMethodsMyOtherInnerClass); + + if (env->RegisterNatives(MyOtherInnerClass_clazz(env), + kMethodsMyOtherInnerClass, + kMethodsMyOtherInnerClassSize) < 0) { + jni_generator::HandleRegistrationError( + env, MyOtherInnerClass_clazz(env), __FILE__); + return false; + } + + const int kMethodsTestJniSize = arraysize(kMethodsTestJni); + + if (env->RegisterNatives(TestJni_clazz(env), + kMethodsTestJni, + kMethodsTestJniSize) < 0) { + jni_generator::HandleRegistrationError( + env, TestJni_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testInnerClassNativesMultiple.golden b/base/android/jni_generator/testInnerClassNativesMultiple.golden new file mode 100644 index 0000000000000000000000000000000000000000..7807efa4002447510a17b069059c2bcb313711c9 --- /dev/null +++ b/base/android/jni_generator/testInnerClassNativesMultiple.golden @@ -0,0 +1,105 @@ +// Copyright 2014 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kMyOtherInnerClassClassPath[] = + "org/chromium/TestJni$MyOtherInnerClass"; +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kMyInnerClassClassPath[] = "org/chromium/TestJni$MyInnerClass"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_MyOtherInnerClass_clazz __attribute__((unused)) = 0; +#define MyOtherInnerClass_clazz(env) base::android::LazyGetClass(env, kMyOtherInnerClassClassPath, &g_MyOtherInnerClass_clazz) +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_MyInnerClass_clazz __attribute__((unused)) = 0; +#define MyInnerClass_clazz(env) base::android::LazyGetClass(env, kMyInnerClassClassPath, &g_MyInnerClass_clazz) + +} // namespace + +// Step 2: method stubs. + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint + Java_org_chromium_TestJni_00024MyInnerClass_nativeInit(JNIEnv* env, jobject + jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint + Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit(JNIEnv* env, + jobject jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsMyOtherInnerClass[] = { + { "nativeInit", +"(" +")" +"I", + reinterpret_cast(Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit) + }, +}; + +static const JNINativeMethod kMethodsMyInnerClass[] = { + { "nativeInit", +"(" +")" +"I", + reinterpret_cast(Java_org_chromium_TestJni_00024MyInnerClass_nativeInit) + }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsMyOtherInnerClassSize = + arraysize(kMethodsMyOtherInnerClass); + + if (env->RegisterNatives(MyOtherInnerClass_clazz(env), + kMethodsMyOtherInnerClass, + kMethodsMyOtherInnerClassSize) < 0) { + jni_generator::HandleRegistrationError( + env, MyOtherInnerClass_clazz(env), __FILE__); + return false; + } + + const int kMethodsMyInnerClassSize = arraysize(kMethodsMyInnerClass); + + if (env->RegisterNatives(MyInnerClass_clazz(env), + kMethodsMyInnerClass, + kMethodsMyInnerClassSize) < 0) { + jni_generator::HandleRegistrationError( + env, MyInnerClass_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testInputStream.javap b/base/android/jni_generator/testInputStream.javap new file mode 100644 index 0000000000000000000000000000000000000000..50ab617a3cc6293c271688049aba77abeed75d37 --- /dev/null +++ b/base/android/jni_generator/testInputStream.javap @@ -0,0 +1,228 @@ +Compiled from "InputStream.java" +public abstract class java.io.InputStream extends java.lang.Object implements java.io.Closeable + SourceFile: "InputStream.java" + minor version: 0 + major version: 49 + Constant pool: +const #1 = Method #6.#39; // java/lang/Object."":()V +const #2 = class #40; // java/lang/RuntimeException +const #3 = String #41; // Stub! +const #4 = Method #2.#42; // java/lang/RuntimeException."":(Ljava/lang/String;)V +const #5 = class #43; // java/io/InputStream +const #6 = class #44; // java/lang/Object +const #7 = class #45; // java/io/Closeable +const #8 = Asciz ; +const #9 = Asciz ()V; +const #10 = Asciz Code; +const #11 = Asciz LineNumberTable; +const #12 = Asciz LocalVariableTable; +const #13 = Asciz this; +const #14 = Asciz Ljava/io/InputStream;; +const #15 = Asciz available; +const #16 = Asciz ()I; +const #17 = Asciz Exceptions; +const #18 = class #46; // java/io/IOException +const #19 = Asciz close; +const #20 = Asciz mark; +const #21 = Asciz (I)V; +const #22 = Asciz readlimit; +const #23 = Asciz I; +const #24 = Asciz markSupported; +const #25 = Asciz ()Z; +const #26 = Asciz read; +const #27 = Asciz ([B)I; +const #28 = Asciz buffer; +const #29 = Asciz [B; +const #30 = Asciz ([BII)I; +const #31 = Asciz byteOffset; +const #32 = Asciz byteCount; +const #33 = Asciz reset; +const #34 = Asciz skip; +const #35 = Asciz (J)J; +const #36 = Asciz J; +const #37 = Asciz SourceFile; +const #38 = Asciz InputStream.java; +const #39 = NameAndType #8:#9;// "":()V +const #40 = Asciz java/lang/RuntimeException; +const #41 = Asciz Stub!; +const #42 = NameAndType #8:#47;// "":(Ljava/lang/String;)V +const #43 = Asciz java/io/InputStream; +const #44 = Asciz java/lang/Object; +const #45 = Asciz java/io/Closeable; +const #46 = Asciz java/io/IOException; +const #47 = Asciz (Ljava/lang/String;)V; + +{ +public java.io.InputStream(); + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: aload_0 + 1: invokespecial #1; //Method java/lang/Object."":()V + 4: new #2; //class java/lang/RuntimeException + 7: dup + 8: ldc #3; //String Stub! + 10: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 13: athrow + LineNumberTable: + line 5: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 14 0 this Ljava/io/InputStream; + + +public int available() throws java.io.IOException; + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 6: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + Exceptions: + throws java.io.IOException +public void close() throws java.io.IOException; + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 7: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + Exceptions: + throws java.io.IOException +public void mark(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 8: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 readlimit I + + +public boolean markSupported(); + Signature: ()Z + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 9: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + +public abstract int read() throws java.io.IOException; + Signature: ()I + Exceptions: + throws java.io.IOException +public int read(byte[]) throws java.io.IOException; + Signature: ([B)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 11: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 buffer [B + + Exceptions: + throws java.io.IOException +public int read(byte[], int, int) throws java.io.IOException; + Signature: ([BII)I + Code: + Stack=3, Locals=4, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 12: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 buffer [B + 0 10 2 byteOffset I + 0 10 3 byteCount I + + Exceptions: + throws java.io.IOException +public synchronized void reset() throws java.io.IOException; + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 13: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + Exceptions: + throws java.io.IOException +public long skip(long) throws java.io.IOException; + Signature: (J)J + Code: + Stack=3, Locals=3, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 14: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 byteCount J + + Exceptions: + throws java.io.IOException +} + diff --git a/base/android/jni_generator/testMotionEvent.javap b/base/android/jni_generator/testMotionEvent.javap new file mode 100644 index 0000000000000000000000000000000000000000..0746943e6ec253e95a8459ceaa3af6929f88e567 --- /dev/null +++ b/base/android/jni_generator/testMotionEvent.javap @@ -0,0 +1,2295 @@ +Compiled from "MotionEvent.java" +public final class android.view.MotionEvent extends android.view.InputEvent implements android.os.Parcelable + SourceFile: "MotionEvent.java" + InnerClass: + public final #10= #9 of #6; //PointerProperties=class android/view/MotionEvent$PointerProperties of class android/view/MotionEvent + public final #13= #12 of #6; //PointerCoords=class android/view/MotionEvent$PointerCoords of class android/view/MotionEvent + public abstract #150= #149 of #8; //Creator=class android/os/Parcelable$Creator of class android/os/Parcelable + minor version: 0 + major version: 49 + Constant pool: +const #1 = Method #7.#293; // android/view/InputEvent."":()V +const #2 = class #294; // java/lang/RuntimeException +const #3 = String #295; // Stub! +const #4 = Method #2.#296; // java/lang/RuntimeException."":(Ljava/lang/String;)V +const #5 = Field #6.#297; // android/view/MotionEvent.CREATOR:Landroid/os/Parcelable$Creator; +const #6 = class #298; // android/view/MotionEvent +const #7 = class #299; // android/view/InputEvent +const #8 = class #300; // android/os/Parcelable +const #9 = class #301; // android/view/MotionEvent$PointerProperties +const #10 = Asciz PointerProperties; +const #11 = Asciz InnerClasses; +const #12 = class #302; // android/view/MotionEvent$PointerCoords +const #13 = Asciz PointerCoords; +const #14 = Asciz INVALID_POINTER_ID; +const #15 = Asciz I; +const #16 = Asciz ConstantValue; +const #17 = int -1; +const #18 = Asciz ACTION_MASK; +const #19 = int 255; +const #20 = Asciz ACTION_DOWN; +const #21 = int 0; +const #22 = Asciz ACTION_UP; +const #23 = int 1; +const #24 = Asciz ACTION_MOVE; +const #25 = int 2; +const #26 = Asciz ACTION_CANCEL; +const #27 = int 3; +const #28 = Asciz ACTION_OUTSIDE; +const #29 = int 4; +const #30 = Asciz ACTION_POINTER_DOWN; +const #31 = int 5; +const #32 = Asciz ACTION_POINTER_UP; +const #33 = int 6; +const #34 = Asciz ACTION_HOVER_MOVE; +const #35 = int 7; +const #36 = Asciz ACTION_SCROLL; +const #37 = int 8; +const #38 = Asciz ACTION_HOVER_ENTER; +const #39 = int 9; +const #40 = Asciz ACTION_HOVER_EXIT; +const #41 = int 10; +const #42 = Asciz ACTION_POINTER_INDEX_MASK; +const #43 = int 65280; +const #44 = Asciz ACTION_POINTER_INDEX_SHIFT; +const #45 = Asciz ACTION_POINTER_1_DOWN; +const #46 = Asciz Deprecated; +const #47 = Asciz RuntimeVisibleAnnotations; +const #48 = Asciz Ljava/lang/Deprecated;; +const #49 = Asciz ACTION_POINTER_2_DOWN; +const #50 = int 261; +const #51 = Asciz ACTION_POINTER_3_DOWN; +const #52 = int 517; +const #53 = Asciz ACTION_POINTER_1_UP; +const #54 = Asciz ACTION_POINTER_2_UP; +const #55 = int 262; +const #56 = Asciz ACTION_POINTER_3_UP; +const #57 = int 518; +const #58 = Asciz ACTION_POINTER_ID_MASK; +const #59 = Asciz ACTION_POINTER_ID_SHIFT; +const #60 = Asciz FLAG_WINDOW_IS_OBSCURED; +const #61 = Asciz EDGE_TOP; +const #62 = Asciz EDGE_BOTTOM; +const #63 = Asciz EDGE_LEFT; +const #64 = Asciz EDGE_RIGHT; +const #65 = Asciz AXIS_X; +const #66 = Asciz AXIS_Y; +const #67 = Asciz AXIS_PRESSURE; +const #68 = Asciz AXIS_SIZE; +const #69 = Asciz AXIS_TOUCH_MAJOR; +const #70 = Asciz AXIS_TOUCH_MINOR; +const #71 = Asciz AXIS_TOOL_MAJOR; +const #72 = Asciz AXIS_TOOL_MINOR; +const #73 = Asciz AXIS_ORIENTATION; +const #74 = Asciz AXIS_VSCROLL; +const #75 = Asciz AXIS_HSCROLL; +const #76 = Asciz AXIS_Z; +const #77 = int 11; +const #78 = Asciz AXIS_RX; +const #79 = int 12; +const #80 = Asciz AXIS_RY; +const #81 = int 13; +const #82 = Asciz AXIS_RZ; +const #83 = int 14; +const #84 = Asciz AXIS_HAT_X; +const #85 = int 15; +const #86 = Asciz AXIS_HAT_Y; +const #87 = int 16; +const #88 = Asciz AXIS_LTRIGGER; +const #89 = int 17; +const #90 = Asciz AXIS_RTRIGGER; +const #91 = int 18; +const #92 = Asciz AXIS_THROTTLE; +const #93 = int 19; +const #94 = Asciz AXIS_RUDDER; +const #95 = int 20; +const #96 = Asciz AXIS_WHEEL; +const #97 = int 21; +const #98 = Asciz AXIS_GAS; +const #99 = int 22; +const #100 = Asciz AXIS_BRAKE; +const #101 = int 23; +const #102 = Asciz AXIS_DISTANCE; +const #103 = int 24; +const #104 = Asciz AXIS_TILT; +const #105 = int 25; +const #106 = Asciz AXIS_GENERIC_1; +const #107 = int 32; +const #108 = Asciz AXIS_GENERIC_2; +const #109 = int 33; +const #110 = Asciz AXIS_GENERIC_3; +const #111 = int 34; +const #112 = Asciz AXIS_GENERIC_4; +const #113 = int 35; +const #114 = Asciz AXIS_GENERIC_5; +const #115 = int 36; +const #116 = Asciz AXIS_GENERIC_6; +const #117 = int 37; +const #118 = Asciz AXIS_GENERIC_7; +const #119 = int 38; +const #120 = Asciz AXIS_GENERIC_8; +const #121 = int 39; +const #122 = Asciz AXIS_GENERIC_9; +const #123 = int 40; +const #124 = Asciz AXIS_GENERIC_10; +const #125 = int 41; +const #126 = Asciz AXIS_GENERIC_11; +const #127 = int 42; +const #128 = Asciz AXIS_GENERIC_12; +const #129 = int 43; +const #130 = Asciz AXIS_GENERIC_13; +const #131 = int 44; +const #132 = Asciz AXIS_GENERIC_14; +const #133 = int 45; +const #134 = Asciz AXIS_GENERIC_15; +const #135 = int 46; +const #136 = Asciz AXIS_GENERIC_16; +const #137 = int 47; +const #138 = Asciz BUTTON_PRIMARY; +const #139 = Asciz BUTTON_SECONDARY; +const #140 = Asciz BUTTON_TERTIARY; +const #141 = Asciz BUTTON_BACK; +const #142 = Asciz BUTTON_FORWARD; +const #143 = Asciz TOOL_TYPE_UNKNOWN; +const #144 = Asciz TOOL_TYPE_FINGER; +const #145 = Asciz TOOL_TYPE_STYLUS; +const #146 = Asciz TOOL_TYPE_MOUSE; +const #147 = Asciz TOOL_TYPE_ERASER; +const #148 = Asciz CREATOR; +const #149 = class #303; // android/os/Parcelable$Creator +const #150 = Asciz Creator; +const #151 = Asciz Landroid/os/Parcelable$Creator;; +const #152 = Asciz Signature; +const #153 = Asciz Landroid/os/Parcelable$Creator;; +const #154 = Asciz ; +const #155 = Asciz ()V; +const #156 = Asciz Code; +const #157 = Asciz LineNumberTable; +const #158 = Asciz LocalVariableTable; +const #159 = Asciz this; +const #160 = Asciz Landroid/view/MotionEvent;; +const #161 = Asciz finalize; +const #162 = Asciz Exceptions; +const #163 = class #304; // java/lang/Throwable +const #164 = Asciz obtain; +const #165 = Asciz (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent;; +const #166 = Asciz downTime; +const #167 = Asciz J; +const #168 = Asciz eventTime; +const #169 = Asciz action; +const #170 = Asciz pointerCount; +const #171 = Asciz pointerProperties; +const #172 = Asciz [Landroid/view/MotionEvent$PointerProperties;; +const #173 = Asciz pointerCoords; +const #174 = Asciz [Landroid/view/MotionEvent$PointerCoords;; +const #175 = Asciz metaState; +const #176 = Asciz buttonState; +const #177 = Asciz xPrecision; +const #178 = Asciz F; +const #179 = Asciz yPrecision; +const #180 = Asciz deviceId; +const #181 = Asciz edgeFlags; +const #182 = Asciz source; +const #183 = Asciz flags; +const #184 = Asciz (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent;; +const #185 = Asciz pointerIds; +const #186 = Asciz [I; +const #187 = Asciz (JJIFFFFIFFII)Landroid/view/MotionEvent;; +const #188 = Asciz x; +const #189 = Asciz y; +const #190 = Asciz pressure; +const #191 = Asciz size; +const #192 = Asciz (JJIIFFFFIFFII)Landroid/view/MotionEvent;; +const #193 = Asciz (JJIFFI)Landroid/view/MotionEvent;; +const #194 = Asciz (Landroid/view/MotionEvent;)Landroid/view/MotionEvent;; +const #195 = Asciz other; +const #196 = Asciz obtainNoHistory; +const #197 = Asciz recycle; +const #198 = Asciz getDeviceId; +const #199 = Asciz ()I; +const #200 = Asciz getSource; +const #201 = Asciz setSource; +const #202 = Asciz (I)V; +const #203 = Asciz getAction; +const #204 = Asciz getActionMasked; +const #205 = Asciz getActionIndex; +const #206 = Asciz getFlags; +const #207 = Asciz getDownTime; +const #208 = Asciz ()J; +const #209 = Asciz getEventTime; +const #210 = Asciz getX; +const #211 = Asciz ()F; +const #212 = Asciz getY; +const #213 = Asciz getPressure; +const #214 = Asciz getSize; +const #215 = Asciz getTouchMajor; +const #216 = Asciz getTouchMinor; +const #217 = Asciz getToolMajor; +const #218 = Asciz getToolMinor; +const #219 = Asciz getOrientation; +const #220 = Asciz getAxisValue; +const #221 = Asciz (I)F; +const #222 = Asciz axis; +const #223 = Asciz getPointerCount; +const #224 = Asciz getPointerId; +const #225 = Asciz (I)I; +const #226 = Asciz pointerIndex; +const #227 = Asciz getToolType; +const #228 = Asciz findPointerIndex; +const #229 = Asciz pointerId; +const #230 = Asciz (II)F; +const #231 = Asciz getPointerCoords; +const #232 = Asciz (ILandroid/view/MotionEvent$PointerCoords;)V; +const #233 = Asciz outPointerCoords; +const #234 = Asciz Landroid/view/MotionEvent$PointerCoords;; +const #235 = Asciz getPointerProperties; +const #236 = Asciz (ILandroid/view/MotionEvent$PointerProperties;)V; +const #237 = Asciz outPointerProperties; +const #238 = Asciz Landroid/view/MotionEvent$PointerProperties;; +const #239 = Asciz getMetaState; +const #240 = Asciz getButtonState; +const #241 = Asciz getRawX; +const #242 = Asciz getRawY; +const #243 = Asciz getXPrecision; +const #244 = Asciz getYPrecision; +const #245 = Asciz getHistorySize; +const #246 = Asciz getHistoricalEventTime; +const #247 = Asciz (I)J; +const #248 = Asciz pos; +const #249 = Asciz getHistoricalX; +const #250 = Asciz getHistoricalY; +const #251 = Asciz getHistoricalPressure; +const #252 = Asciz getHistoricalSize; +const #253 = Asciz getHistoricalTouchMajor; +const #254 = Asciz getHistoricalTouchMinor; +const #255 = Asciz getHistoricalToolMajor; +const #256 = Asciz getHistoricalToolMinor; +const #257 = Asciz getHistoricalOrientation; +const #258 = Asciz getHistoricalAxisValue; +const #259 = Asciz (III)F; +const #260 = Asciz getHistoricalPointerCoords; +const #261 = Asciz (IILandroid/view/MotionEvent$PointerCoords;)V; +const #262 = Asciz getEdgeFlags; +const #263 = Asciz setEdgeFlags; +const #264 = Asciz setAction; +const #265 = Asciz offsetLocation; +const #266 = Asciz (FF)V; +const #267 = Asciz deltaX; +const #268 = Asciz deltaY; +const #269 = Asciz setLocation; +const #270 = Asciz transform; +const #271 = Asciz (Landroid/graphics/Matrix;)V; +const #272 = Asciz matrix; +const #273 = Asciz Landroid/graphics/Matrix;; +const #274 = Asciz addBatch; +const #275 = Asciz (JFFFFI)V; +const #276 = Asciz (J[Landroid/view/MotionEvent$PointerCoords;I)V; +const #277 = Asciz toString; +const #278 = Asciz ()Ljava/lang/String;; +const #279 = Asciz actionToString; +const #280 = Asciz (I)Ljava/lang/String;; +const #281 = Asciz axisToString; +const #282 = Asciz axisFromString; +const #283 = Asciz (Ljava/lang/String;)I; +const #284 = Asciz symbolicName; +const #285 = Asciz Ljava/lang/String;; +const #286 = Asciz writeToParcel; +const #287 = Asciz (Landroid/os/Parcel;I)V; +const #288 = Asciz out; +const #289 = Asciz Landroid/os/Parcel;; +const #290 = Asciz ; +const #291 = Asciz SourceFile; +const #292 = Asciz MotionEvent.java; +const #293 = NameAndType #154:#155;// "":()V +const #294 = Asciz java/lang/RuntimeException; +const #295 = Asciz Stub!; +const #296 = NameAndType #154:#305;// "":(Ljava/lang/String;)V +const #297 = NameAndType #148:#151;// CREATOR:Landroid/os/Parcelable$Creator; +const #298 = Asciz android/view/MotionEvent; +const #299 = Asciz android/view/InputEvent; +const #300 = Asciz android/os/Parcelable; +const #301 = Asciz android/view/MotionEvent$PointerProperties; +const #302 = Asciz android/view/MotionEvent$PointerCoords; +const #303 = Asciz android/os/Parcelable$Creator; +const #304 = Asciz java/lang/Throwable; +const #305 = Asciz (Ljava/lang/String;)V; + +{ +public static final int INVALID_POINTER_ID; + Signature: I + Constant value: int -1 + +public static final int ACTION_MASK; + Signature: I + Constant value: int 255 + +public static final int ACTION_DOWN; + Signature: I + Constant value: int 0 + +public static final int ACTION_UP; + Signature: I + Constant value: int 1 + +public static final int ACTION_MOVE; + Signature: I + Constant value: int 2 + +public static final int ACTION_CANCEL; + Signature: I + Constant value: int 3 + +public static final int ACTION_OUTSIDE; + Signature: I + Constant value: int 4 + +public static final int ACTION_POINTER_DOWN; + Signature: I + Constant value: int 5 + +public static final int ACTION_POINTER_UP; + Signature: I + Constant value: int 6 + +public static final int ACTION_HOVER_MOVE; + Signature: I + Constant value: int 7 + +public static final int ACTION_SCROLL; + Signature: I + Constant value: int 8 + +public static final int ACTION_HOVER_ENTER; + Signature: I + Constant value: int 9 + +public static final int ACTION_HOVER_EXIT; + Signature: I + Constant value: int 10 + +public static final int ACTION_POINTER_INDEX_MASK; + Signature: I + Constant value: int 65280 + +public static final int ACTION_POINTER_INDEX_SHIFT; + Signature: I + Constant value: int 8 + +public static final int ACTION_POINTER_1_DOWN; + Signature: I + Constant value: int 5Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_2_DOWN; + Signature: I + Constant value: int 261Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_3_DOWN; + Signature: I + Constant value: int 517Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_1_UP; + Signature: I + Constant value: int 6Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_2_UP; + Signature: I + Constant value: int 262Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_3_UP; + Signature: I + Constant value: int 518Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_ID_MASK; + Signature: I + Constant value: int 65280Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_ID_SHIFT; + Signature: I + Constant value: int 8Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int FLAG_WINDOW_IS_OBSCURED; + Signature: I + Constant value: int 1 + +public static final int EDGE_TOP; + Signature: I + Constant value: int 1 + +public static final int EDGE_BOTTOM; + Signature: I + Constant value: int 2 + +public static final int EDGE_LEFT; + Signature: I + Constant value: int 4 + +public static final int EDGE_RIGHT; + Signature: I + Constant value: int 8 + +public static final int AXIS_X; + Signature: I + Constant value: int 0 + +public static final int AXIS_Y; + Signature: I + Constant value: int 1 + +public static final int AXIS_PRESSURE; + Signature: I + Constant value: int 2 + +public static final int AXIS_SIZE; + Signature: I + Constant value: int 3 + +public static final int AXIS_TOUCH_MAJOR; + Signature: I + Constant value: int 4 + +public static final int AXIS_TOUCH_MINOR; + Signature: I + Constant value: int 5 + +public static final int AXIS_TOOL_MAJOR; + Signature: I + Constant value: int 6 + +public static final int AXIS_TOOL_MINOR; + Signature: I + Constant value: int 7 + +public static final int AXIS_ORIENTATION; + Signature: I + Constant value: int 8 + +public static final int AXIS_VSCROLL; + Signature: I + Constant value: int 9 + +public static final int AXIS_HSCROLL; + Signature: I + Constant value: int 10 + +public static final int AXIS_Z; + Signature: I + Constant value: int 11 + +public static final int AXIS_RX; + Signature: I + Constant value: int 12 + +public static final int AXIS_RY; + Signature: I + Constant value: int 13 + +public static final int AXIS_RZ; + Signature: I + Constant value: int 14 + +public static final int AXIS_HAT_X; + Signature: I + Constant value: int 15 + +public static final int AXIS_HAT_Y; + Signature: I + Constant value: int 16 + +public static final int AXIS_LTRIGGER; + Signature: I + Constant value: int 17 + +public static final int AXIS_RTRIGGER; + Signature: I + Constant value: int 18 + +public static final int AXIS_THROTTLE; + Signature: I + Constant value: int 19 + +public static final int AXIS_RUDDER; + Signature: I + Constant value: int 20 + +public static final int AXIS_WHEEL; + Signature: I + Constant value: int 21 + +public static final int AXIS_GAS; + Signature: I + Constant value: int 22 + +public static final int AXIS_BRAKE; + Signature: I + Constant value: int 23 + +public static final int AXIS_DISTANCE; + Signature: I + Constant value: int 24 + +public static final int AXIS_TILT; + Signature: I + Constant value: int 25 + +public static final int AXIS_GENERIC_1; + Signature: I + Constant value: int 32 + +public static final int AXIS_GENERIC_2; + Signature: I + Constant value: int 33 + +public static final int AXIS_GENERIC_3; + Signature: I + Constant value: int 34 + +public static final int AXIS_GENERIC_4; + Signature: I + Constant value: int 35 + +public static final int AXIS_GENERIC_5; + Signature: I + Constant value: int 36 + +public static final int AXIS_GENERIC_6; + Signature: I + Constant value: int 37 + +public static final int AXIS_GENERIC_7; + Signature: I + Constant value: int 38 + +public static final int AXIS_GENERIC_8; + Signature: I + Constant value: int 39 + +public static final int AXIS_GENERIC_9; + Signature: I + Constant value: int 40 + +public static final int AXIS_GENERIC_10; + Signature: I + Constant value: int 41 + +public static final int AXIS_GENERIC_11; + Signature: I + Constant value: int 42 + +public static final int AXIS_GENERIC_12; + Signature: I + Constant value: int 43 + +public static final int AXIS_GENERIC_13; + Signature: I + Constant value: int 44 + +public static final int AXIS_GENERIC_14; + Signature: I + Constant value: int 45 + +public static final int AXIS_GENERIC_15; + Signature: I + Constant value: int 46 + +public static final int AXIS_GENERIC_16; + Signature: I + Constant value: int 47 + +public static final int BUTTON_PRIMARY; + Signature: I + Constant value: int 1 + +public static final int BUTTON_SECONDARY; + Signature: I + Constant value: int 2 + +public static final int BUTTON_TERTIARY; + Signature: I + Constant value: int 4 + +public static final int BUTTON_BACK; + Signature: I + Constant value: int 8 + +public static final int BUTTON_FORWARD; + Signature: I + Constant value: int 16 + +public static final int TOOL_TYPE_UNKNOWN; + Signature: I + Constant value: int 0 + +public static final int TOOL_TYPE_FINGER; + Signature: I + Constant value: int 1 + +public static final int TOOL_TYPE_STYLUS; + Signature: I + Constant value: int 2 + +public static final int TOOL_TYPE_MOUSE; + Signature: I + Constant value: int 3 + +public static final int TOOL_TYPE_ERASER; + Signature: I + Constant value: int 4 + +public static final android.os.Parcelable$Creator CREATOR; + Signature: Landroid/os/Parcelable$Creator; + Signature: length = 0x2 + 00 FFFFFF99 + + +android.view.MotionEvent(); + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: aload_0 + 1: invokespecial #1; //Method android/view/InputEvent."":()V + 4: new #2; //class java/lang/RuntimeException + 7: dup + 8: ldc #3; //String Stub! + 10: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 13: athrow + LineNumberTable: + line 35: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 14 0 this Landroid/view/MotionEvent; + + +protected void finalize() throws java.lang.Throwable; + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 36: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + Exceptions: + throws java.lang.Throwable +public static android.view.MotionEvent obtain(long, long, int, int, android.view.MotionEvent$PointerProperties[], android.view.MotionEvent$PointerCoords[], int, int, float, float, int, int, int, int); + Signature: (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=16, Args_size=14 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 37: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerProperties [Landroid/view/MotionEvent$PointerProperties; + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 buttonState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + 0 10 14 source I + 0 10 15 flags I + + +public static android.view.MotionEvent obtain(long, long, int, int, int[], android.view.MotionEvent$PointerCoords[], int, float, float, int, int, int, int); + Signature: (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=15, Args_size=13 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 39: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerIds [I + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 xPrecision F + 0 10 10 yPrecision F + 0 10 11 deviceId I + 0 10 12 edgeFlags I + 0 10 13 source I + 0 10 14 flags I + + Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + +public static android.view.MotionEvent obtain(long, long, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIFFFFIFFII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=14, Args_size=12 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 40: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 pressure F + 0 10 8 size F + 0 10 9 metaState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + + +public static android.view.MotionEvent obtain(long, long, int, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIIFFFFIFFII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=15, Args_size=13 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 42: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 x F + 0 10 7 y F + 0 10 8 pressure F + 0 10 9 size F + 0 10 10 metaState I + 0 10 11 xPrecision F + 0 10 12 yPrecision F + 0 10 13 deviceId I + 0 10 14 edgeFlags I + + Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + +public static android.view.MotionEvent obtain(long, long, int, float, float, int); + Signature: (JJIFFI)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=8, Args_size=6 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 43: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 metaState I + + +public static android.view.MotionEvent obtain(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 44: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + +public static android.view.MotionEvent obtainNoHistory(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 45: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + +public final void recycle(); + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 46: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getDeviceId(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 47: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getSource(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 48: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final void setSource(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 49: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 source I + + +public final int getAction(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 50: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getActionMasked(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 51: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getActionIndex(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 52: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getFlags(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 53: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final long getDownTime(); + Signature: ()J + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 54: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final long getEventTime(); + Signature: ()J + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 55: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getX(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 56: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getY(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 57: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getPressure(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 58: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getSize(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 59: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getTouchMajor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 60: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getTouchMinor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 61: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getToolMajor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 62: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getToolMinor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 63: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getOrientation(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 64: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getAxisValue(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 65: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + + +public final int getPointerCount(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 66: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getPointerId(int); + Signature: (I)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 67: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final int getToolType(int); + Signature: (I)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 68: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final int findPointerIndex(int); + Signature: (I)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 69: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerId I + + +public final float getX(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 70: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getY(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 71: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getPressure(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 72: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getSize(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 73: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getTouchMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 74: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getTouchMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 75: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getToolMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 76: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getToolMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 77: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getOrientation(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 78: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getAxisValue(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 79: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + + +public final void getPointerCoords(int, android.view.MotionEvent$PointerCoords); + Signature: (ILandroid/view/MotionEvent$PointerCoords;)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 80: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + +public final void getPointerProperties(int, android.view.MotionEvent$PointerProperties); + Signature: (ILandroid/view/MotionEvent$PointerProperties;)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 81: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerProperties Landroid/view/MotionEvent$PointerProperties; + + +public final int getMetaState(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 82: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getButtonState(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 83: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getRawX(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 84: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getRawY(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 85: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getXPrecision(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 86: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getYPrecision(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 87: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getHistorySize(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 88: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final long getHistoricalEventTime(int); + Signature: (I)J + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 89: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalX(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 90: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalY(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 91: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalPressure(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 92: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalSize(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 93: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalTouchMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 94: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalTouchMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 95: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalToolMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 96: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalToolMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 97: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalOrientation(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 98: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalAxisValue(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 99: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pos I + + +public final float getHistoricalX(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 100: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalY(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 101: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalPressure(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 102: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalSize(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 103: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalTouchMajor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 104: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalTouchMinor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 105: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalToolMajor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 106: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalToolMinor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 107: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalOrientation(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 108: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalAxisValue(int, int, int); + Signature: (III)F + Code: + Stack=3, Locals=4, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 109: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + 0 10 3 pos I + + +public final void getHistoricalPointerCoords(int, int, android.view.MotionEvent$PointerCoords); + Signature: (IILandroid/view/MotionEvent$PointerCoords;)V + Code: + Stack=3, Locals=4, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 110: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + 0 10 3 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + +public final int getEdgeFlags(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 111: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final void setEdgeFlags(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 112: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 flags I + + +public final void setAction(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 113: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 action I + + +public final void offsetLocation(float, float); + Signature: (FF)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 114: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 deltaX F + 0 10 2 deltaY F + + +public final void setLocation(float, float); + Signature: (FF)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 115: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 x F + 0 10 2 y F + + +public final void transform(android.graphics.Matrix); + Signature: (Landroid/graphics/Matrix;)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 116: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 matrix Landroid/graphics/Matrix; + + +public final void addBatch(long, float, float, float, float, int); + Signature: (JFFFFI)V + Code: + Stack=3, Locals=8, Args_size=7 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 117: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 x F + 0 10 4 y F + 0 10 5 pressure F + 0 10 6 size F + 0 10 7 metaState I + + +public final void addBatch(long, android.view.MotionEvent$PointerCoords[], int); + Signature: (J[Landroid/view/MotionEvent$PointerCoords;I)V + Code: + Stack=3, Locals=5, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 118: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 4 metaState I + + +public java.lang.String toString(); + Signature: ()Ljava/lang/String; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 119: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public static java.lang.String actionToString(int); + Signature: (I)Ljava/lang/String; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 120: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 action I + + +public static java.lang.String axisToString(int); + Signature: (I)Ljava/lang/String; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 121: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 axis I + + +public static int axisFromString(java.lang.String); + Signature: (Ljava/lang/String;)I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 122: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 symbolicName Ljava/lang/String; + + +public void writeToParcel(android.os.Parcel, int); + Signature: (Landroid/os/Parcel;I)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 123: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 out Landroid/os/Parcel; + 0 10 2 flags I + + +static {}; + Signature: ()V + Code: + Stack=1, Locals=0, Args_size=0 + 0: aconst_null + 1: putstatic #5; //Field CREATOR:Landroid/os/Parcelable$Creator; + 4: return + LineNumberTable: + line 213: 0 + + +} + diff --git a/base/android/jni_generator/testMotionEvent.javap7 b/base/android/jni_generator/testMotionEvent.javap7 new file mode 100644 index 0000000000000000000000000000000000000000..f4f54443696a681df810a4b43595125256236e56 --- /dev/null +++ b/base/android/jni_generator/testMotionEvent.javap7 @@ -0,0 +1,2370 @@ +Classfile out_android/Debug/gen/content/jni/android/view/MotionEvent.class + Last modified Feb 27, 2014; size 13369 bytes + MD5 checksum 3718d77a994cb8aceb7b35c5df3c4dd1 + Compiled from "MotionEvent.java" +public final class android.view.MotionEvent extends android.view.InputEvent implements android.os.Parcelable + SourceFile: "MotionEvent.java" + InnerClasses: + public static final #10= #9 of #6; //PointerProperties=class android/view/MotionEvent$PointerProperties of class android/view/MotionEvent + public static final #13= #12 of #6; //PointerCoords=class android/view/MotionEvent$PointerCoords of class android/view/MotionEvent + public static #150= #149 of #8; //Creator=class android/os/Parcelable$Creator of class android/os/Parcelable + minor version: 0 + major version: 49 + flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER +Constant pool: + #1 = Methodref #7.#293 // android/view/InputEvent."":()V + #2 = Class #294 // java/lang/RuntimeException + #3 = String #295 // Stub! + #4 = Methodref #2.#296 // java/lang/RuntimeException."":(Ljava/lang/String;)V + #5 = Fieldref #6.#297 // android/view/MotionEvent.CREATOR:Landroid/os/Parcelable$Creator; + #6 = Class #298 // android/view/MotionEvent + #7 = Class #299 // android/view/InputEvent + #8 = Class #300 // android/os/Parcelable + #9 = Class #301 // android/view/MotionEvent$PointerProperties + #10 = Utf8 PointerProperties + #11 = Utf8 InnerClasses + #12 = Class #302 // android/view/MotionEvent$PointerCoords + #13 = Utf8 PointerCoords + #14 = Utf8 INVALID_POINTER_ID + #15 = Utf8 I + #16 = Utf8 ConstantValue + #17 = Integer -1 + #18 = Utf8 ACTION_MASK + #19 = Integer 255 + #20 = Utf8 ACTION_DOWN + #21 = Integer 0 + #22 = Utf8 ACTION_UP + #23 = Integer 1 + #24 = Utf8 ACTION_MOVE + #25 = Integer 2 + #26 = Utf8 ACTION_CANCEL + #27 = Integer 3 + #28 = Utf8 ACTION_OUTSIDE + #29 = Integer 4 + #30 = Utf8 ACTION_POINTER_DOWN + #31 = Integer 5 + #32 = Utf8 ACTION_POINTER_UP + #33 = Integer 6 + #34 = Utf8 ACTION_HOVER_MOVE + #35 = Integer 7 + #36 = Utf8 ACTION_SCROLL + #37 = Integer 8 + #38 = Utf8 ACTION_HOVER_ENTER + #39 = Integer 9 + #40 = Utf8 ACTION_HOVER_EXIT + #41 = Integer 10 + #42 = Utf8 ACTION_POINTER_INDEX_MASK + #43 = Integer 65280 + #44 = Utf8 ACTION_POINTER_INDEX_SHIFT + #45 = Utf8 ACTION_POINTER_1_DOWN + #46 = Utf8 Deprecated + #47 = Utf8 RuntimeVisibleAnnotations + #48 = Utf8 Ljava/lang/Deprecated; + #49 = Utf8 ACTION_POINTER_2_DOWN + #50 = Integer 261 + #51 = Utf8 ACTION_POINTER_3_DOWN + #52 = Integer 517 + #53 = Utf8 ACTION_POINTER_1_UP + #54 = Utf8 ACTION_POINTER_2_UP + #55 = Integer 262 + #56 = Utf8 ACTION_POINTER_3_UP + #57 = Integer 518 + #58 = Utf8 ACTION_POINTER_ID_MASK + #59 = Utf8 ACTION_POINTER_ID_SHIFT + #60 = Utf8 FLAG_WINDOW_IS_OBSCURED + #61 = Utf8 EDGE_TOP + #62 = Utf8 EDGE_BOTTOM + #63 = Utf8 EDGE_LEFT + #64 = Utf8 EDGE_RIGHT + #65 = Utf8 AXIS_X + #66 = Utf8 AXIS_Y + #67 = Utf8 AXIS_PRESSURE + #68 = Utf8 AXIS_SIZE + #69 = Utf8 AXIS_TOUCH_MAJOR + #70 = Utf8 AXIS_TOUCH_MINOR + #71 = Utf8 AXIS_TOOL_MAJOR + #72 = Utf8 AXIS_TOOL_MINOR + #73 = Utf8 AXIS_ORIENTATION + #74 = Utf8 AXIS_VSCROLL + #75 = Utf8 AXIS_HSCROLL + #76 = Utf8 AXIS_Z + #77 = Integer 11 + #78 = Utf8 AXIS_RX + #79 = Integer 12 + #80 = Utf8 AXIS_RY + #81 = Integer 13 + #82 = Utf8 AXIS_RZ + #83 = Integer 14 + #84 = Utf8 AXIS_HAT_X + #85 = Integer 15 + #86 = Utf8 AXIS_HAT_Y + #87 = Integer 16 + #88 = Utf8 AXIS_LTRIGGER + #89 = Integer 17 + #90 = Utf8 AXIS_RTRIGGER + #91 = Integer 18 + #92 = Utf8 AXIS_THROTTLE + #93 = Integer 19 + #94 = Utf8 AXIS_RUDDER + #95 = Integer 20 + #96 = Utf8 AXIS_WHEEL + #97 = Integer 21 + #98 = Utf8 AXIS_GAS + #99 = Integer 22 + #100 = Utf8 AXIS_BRAKE + #101 = Integer 23 + #102 = Utf8 AXIS_DISTANCE + #103 = Integer 24 + #104 = Utf8 AXIS_TILT + #105 = Integer 25 + #106 = Utf8 AXIS_GENERIC_1 + #107 = Integer 32 + #108 = Utf8 AXIS_GENERIC_2 + #109 = Integer 33 + #110 = Utf8 AXIS_GENERIC_3 + #111 = Integer 34 + #112 = Utf8 AXIS_GENERIC_4 + #113 = Integer 35 + #114 = Utf8 AXIS_GENERIC_5 + #115 = Integer 36 + #116 = Utf8 AXIS_GENERIC_6 + #117 = Integer 37 + #118 = Utf8 AXIS_GENERIC_7 + #119 = Integer 38 + #120 = Utf8 AXIS_GENERIC_8 + #121 = Integer 39 + #122 = Utf8 AXIS_GENERIC_9 + #123 = Integer 40 + #124 = Utf8 AXIS_GENERIC_10 + #125 = Integer 41 + #126 = Utf8 AXIS_GENERIC_11 + #127 = Integer 42 + #128 = Utf8 AXIS_GENERIC_12 + #129 = Integer 43 + #130 = Utf8 AXIS_GENERIC_13 + #131 = Integer 44 + #132 = Utf8 AXIS_GENERIC_14 + #133 = Integer 45 + #134 = Utf8 AXIS_GENERIC_15 + #135 = Integer 46 + #136 = Utf8 AXIS_GENERIC_16 + #137 = Integer 47 + #138 = Utf8 BUTTON_PRIMARY + #139 = Utf8 BUTTON_SECONDARY + #140 = Utf8 BUTTON_TERTIARY + #141 = Utf8 BUTTON_BACK + #142 = Utf8 BUTTON_FORWARD + #143 = Utf8 TOOL_TYPE_UNKNOWN + #144 = Utf8 TOOL_TYPE_FINGER + #145 = Utf8 TOOL_TYPE_STYLUS + #146 = Utf8 TOOL_TYPE_MOUSE + #147 = Utf8 TOOL_TYPE_ERASER + #148 = Utf8 CREATOR + #149 = Class #303 // android/os/Parcelable$Creator + #150 = Utf8 Creator + #151 = Utf8 Landroid/os/Parcelable$Creator; + #152 = Utf8 Signature + #153 = Utf8 Landroid/os/Parcelable$Creator; + #154 = Utf8 + #155 = Utf8 ()V + #156 = Utf8 Code + #157 = Utf8 LineNumberTable + #158 = Utf8 LocalVariableTable + #159 = Utf8 this + #160 = Utf8 Landroid/view/MotionEvent; + #161 = Utf8 finalize + #162 = Utf8 Exceptions + #163 = Class #304 // java/lang/Throwable + #164 = Utf8 obtain + #165 = Utf8 (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent; + #166 = Utf8 downTime + #167 = Utf8 J + #168 = Utf8 eventTime + #169 = Utf8 action + #170 = Utf8 pointerCount + #171 = Utf8 pointerProperties + #172 = Utf8 [Landroid/view/MotionEvent$PointerProperties; + #173 = Utf8 pointerCoords + #174 = Utf8 [Landroid/view/MotionEvent$PointerCoords; + #175 = Utf8 metaState + #176 = Utf8 buttonState + #177 = Utf8 xPrecision + #178 = Utf8 F + #179 = Utf8 yPrecision + #180 = Utf8 deviceId + #181 = Utf8 edgeFlags + #182 = Utf8 source + #183 = Utf8 flags + #184 = Utf8 (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent; + #185 = Utf8 pointerIds + #186 = Utf8 [I + #187 = Utf8 (JJIFFFFIFFII)Landroid/view/MotionEvent; + #188 = Utf8 x + #189 = Utf8 y + #190 = Utf8 pressure + #191 = Utf8 size + #192 = Utf8 (JJIIFFFFIFFII)Landroid/view/MotionEvent; + #193 = Utf8 (JJIFFI)Landroid/view/MotionEvent; + #194 = Utf8 (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + #195 = Utf8 other + #196 = Utf8 obtainNoHistory + #197 = Utf8 recycle + #198 = Utf8 getDeviceId + #199 = Utf8 ()I + #200 = Utf8 getSource + #201 = Utf8 setSource + #202 = Utf8 (I)V + #203 = Utf8 getAction + #204 = Utf8 getActionMasked + #205 = Utf8 getActionIndex + #206 = Utf8 getFlags + #207 = Utf8 getDownTime + #208 = Utf8 ()J + #209 = Utf8 getEventTime + #210 = Utf8 getX + #211 = Utf8 ()F + #212 = Utf8 getY + #213 = Utf8 getPressure + #214 = Utf8 getSize + #215 = Utf8 getTouchMajor + #216 = Utf8 getTouchMinor + #217 = Utf8 getToolMajor + #218 = Utf8 getToolMinor + #219 = Utf8 getOrientation + #220 = Utf8 getAxisValue + #221 = Utf8 (I)F + #222 = Utf8 axis + #223 = Utf8 getPointerCount + #224 = Utf8 getPointerId + #225 = Utf8 (I)I + #226 = Utf8 pointerIndex + #227 = Utf8 getToolType + #228 = Utf8 findPointerIndex + #229 = Utf8 pointerId + #230 = Utf8 (II)F + #231 = Utf8 getPointerCoords + #232 = Utf8 (ILandroid/view/MotionEvent$PointerCoords;)V + #233 = Utf8 outPointerCoords + #234 = Utf8 Landroid/view/MotionEvent$PointerCoords; + #235 = Utf8 getPointerProperties + #236 = Utf8 (ILandroid/view/MotionEvent$PointerProperties;)V + #237 = Utf8 outPointerProperties + #238 = Utf8 Landroid/view/MotionEvent$PointerProperties; + #239 = Utf8 getMetaState + #240 = Utf8 getButtonState + #241 = Utf8 getRawX + #242 = Utf8 getRawY + #243 = Utf8 getXPrecision + #244 = Utf8 getYPrecision + #245 = Utf8 getHistorySize + #246 = Utf8 getHistoricalEventTime + #247 = Utf8 (I)J + #248 = Utf8 pos + #249 = Utf8 getHistoricalX + #250 = Utf8 getHistoricalY + #251 = Utf8 getHistoricalPressure + #252 = Utf8 getHistoricalSize + #253 = Utf8 getHistoricalTouchMajor + #254 = Utf8 getHistoricalTouchMinor + #255 = Utf8 getHistoricalToolMajor + #256 = Utf8 getHistoricalToolMinor + #257 = Utf8 getHistoricalOrientation + #258 = Utf8 getHistoricalAxisValue + #259 = Utf8 (III)F + #260 = Utf8 getHistoricalPointerCoords + #261 = Utf8 (IILandroid/view/MotionEvent$PointerCoords;)V + #262 = Utf8 getEdgeFlags + #263 = Utf8 setEdgeFlags + #264 = Utf8 setAction + #265 = Utf8 offsetLocation + #266 = Utf8 (FF)V + #267 = Utf8 deltaX + #268 = Utf8 deltaY + #269 = Utf8 setLocation + #270 = Utf8 transform + #271 = Utf8 (Landroid/graphics/Matrix;)V + #272 = Utf8 matrix + #273 = Utf8 Landroid/graphics/Matrix; + #274 = Utf8 addBatch + #275 = Utf8 (JFFFFI)V + #276 = Utf8 (J[Landroid/view/MotionEvent$PointerCoords;I)V + #277 = Utf8 toString + #278 = Utf8 ()Ljava/lang/String; + #279 = Utf8 actionToString + #280 = Utf8 (I)Ljava/lang/String; + #281 = Utf8 axisToString + #282 = Utf8 axisFromString + #283 = Utf8 (Ljava/lang/String;)I + #284 = Utf8 symbolicName + #285 = Utf8 Ljava/lang/String; + #286 = Utf8 writeToParcel + #287 = Utf8 (Landroid/os/Parcel;I)V + #288 = Utf8 out + #289 = Utf8 Landroid/os/Parcel; + #290 = Utf8 + #291 = Utf8 SourceFile + #292 = Utf8 MotionEvent.java + #293 = NameAndType #154:#155 // "":()V + #294 = Utf8 java/lang/RuntimeException + #295 = Utf8 Stub! + #296 = NameAndType #154:#305 // "":(Ljava/lang/String;)V + #297 = NameAndType #148:#151 // CREATOR:Landroid/os/Parcelable$Creator; + #298 = Utf8 android/view/MotionEvent + #299 = Utf8 android/view/InputEvent + #300 = Utf8 android/os/Parcelable + #301 = Utf8 android/view/MotionEvent$PointerProperties + #302 = Utf8 android/view/MotionEvent$PointerCoords + #303 = Utf8 android/os/Parcelable$Creator + #304 = Utf8 java/lang/Throwable + #305 = Utf8 (Ljava/lang/String;)V +{ + public static final int INVALID_POINTER_ID; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int -1 + + + public static final int ACTION_MASK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 255 + + + public static final int ACTION_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 0 + + + public static final int ACTION_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int ACTION_MOVE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int ACTION_CANCEL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 3 + + + public static final int ACTION_OUTSIDE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int ACTION_POINTER_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 5 + + + public static final int ACTION_POINTER_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 6 + + + public static final int ACTION_HOVER_MOVE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 7 + + + public static final int ACTION_SCROLL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int ACTION_HOVER_ENTER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 9 + + + public static final int ACTION_HOVER_EXIT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 10 + + + public static final int ACTION_POINTER_INDEX_MASK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 65280 + + + public static final int ACTION_POINTER_INDEX_SHIFT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int ACTION_POINTER_1_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 5 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_2_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 261 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_3_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 517 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_1_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 6 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_2_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 262 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_3_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 518 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_ID_MASK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 65280 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_ID_SHIFT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int FLAG_WINDOW_IS_OBSCURED; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int EDGE_TOP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int EDGE_BOTTOM; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int EDGE_LEFT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int EDGE_RIGHT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int AXIS_X; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 0 + + + public static final int AXIS_Y; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int AXIS_PRESSURE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int AXIS_SIZE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 3 + + + public static final int AXIS_TOUCH_MAJOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int AXIS_TOUCH_MINOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 5 + + + public static final int AXIS_TOOL_MAJOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 6 + + + public static final int AXIS_TOOL_MINOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 7 + + + public static final int AXIS_ORIENTATION; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int AXIS_VSCROLL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 9 + + + public static final int AXIS_HSCROLL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 10 + + + public static final int AXIS_Z; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 11 + + + public static final int AXIS_RX; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 12 + + + public static final int AXIS_RY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 13 + + + public static final int AXIS_RZ; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 14 + + + public static final int AXIS_HAT_X; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 15 + + + public static final int AXIS_HAT_Y; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 16 + + + public static final int AXIS_LTRIGGER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 17 + + + public static final int AXIS_RTRIGGER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 18 + + + public static final int AXIS_THROTTLE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 19 + + + public static final int AXIS_RUDDER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 20 + + + public static final int AXIS_WHEEL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 21 + + + public static final int AXIS_GAS; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 22 + + + public static final int AXIS_BRAKE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 23 + + + public static final int AXIS_DISTANCE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 24 + + + public static final int AXIS_TILT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 25 + + + public static final int AXIS_GENERIC_1; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 32 + + + public static final int AXIS_GENERIC_2; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 33 + + + public static final int AXIS_GENERIC_3; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 34 + + + public static final int AXIS_GENERIC_4; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 35 + + + public static final int AXIS_GENERIC_5; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 36 + + + public static final int AXIS_GENERIC_6; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 37 + + + public static final int AXIS_GENERIC_7; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 38 + + + public static final int AXIS_GENERIC_8; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 39 + + + public static final int AXIS_GENERIC_9; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 40 + + + public static final int AXIS_GENERIC_10; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 41 + + + public static final int AXIS_GENERIC_11; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 42 + + + public static final int AXIS_GENERIC_12; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 43 + + + public static final int AXIS_GENERIC_13; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 44 + + + public static final int AXIS_GENERIC_14; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 45 + + + public static final int AXIS_GENERIC_15; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 46 + + + public static final int AXIS_GENERIC_16; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 47 + + + public static final int BUTTON_PRIMARY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int BUTTON_SECONDARY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int BUTTON_TERTIARY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int BUTTON_BACK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int BUTTON_FORWARD; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 16 + + + public static final int TOOL_TYPE_UNKNOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 0 + + + public static final int TOOL_TYPE_FINGER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int TOOL_TYPE_STYLUS; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int TOOL_TYPE_MOUSE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 3 + + + public static final int TOOL_TYPE_ERASER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final android.os.Parcelable$Creator CREATOR; + Signature: Landroid/os/Parcelable$Creator; + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + Signature: #153 // Landroid/os/Parcelable$Creator; + + + android.view.MotionEvent(); + Signature: ()V + flags: + Code: + stack=3, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #1 // Method android/view/InputEvent."":()V + 4: new #2 // class java/lang/RuntimeException + 7: dup + 8: ldc #3 // String Stub! + 10: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 13: athrow + LineNumberTable: + line 35: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 14 0 this Landroid/view/MotionEvent; + + protected void finalize() throws java.lang.Throwable; + Signature: ()V + flags: ACC_PROTECTED + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 36: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + Exceptions: + throws java.lang.Throwable + + public static android.view.MotionEvent obtain(long, long, int, int, android.view.MotionEvent$PointerProperties[], android.view.MotionEvent$PointerCoords[], int, int, float, float, int, int, int, int); + Signature: (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=16, args_size=14 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 37: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerProperties [Landroid/view/MotionEvent$PointerProperties; + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 buttonState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + 0 10 14 source I + 0 10 15 flags I + + public static android.view.MotionEvent obtain(long, long, int, int, int[], android.view.MotionEvent$PointerCoords[], int, float, float, int, int, int, int); + Signature: (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=15, args_size=13 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 39: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerIds [I + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 xPrecision F + 0 10 10 yPrecision F + 0 10 11 deviceId I + 0 10 12 edgeFlags I + 0 10 13 source I + 0 10 14 flags I + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + public static android.view.MotionEvent obtain(long, long, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIFFFFIFFII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=14, args_size=12 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 40: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 pressure F + 0 10 8 size F + 0 10 9 metaState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + + public static android.view.MotionEvent obtain(long, long, int, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIIFFFFIFFII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=15, args_size=13 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 42: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 x F + 0 10 7 y F + 0 10 8 pressure F + 0 10 9 size F + 0 10 10 metaState I + 0 10 11 xPrecision F + 0 10 12 yPrecision F + 0 10 13 deviceId I + 0 10 14 edgeFlags I + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + public static android.view.MotionEvent obtain(long, long, int, float, float, int); + Signature: (JJIFFI)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=8, args_size=6 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 43: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 metaState I + + public static android.view.MotionEvent obtain(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 44: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + public static android.view.MotionEvent obtainNoHistory(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 45: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + public final void recycle(); + Signature: ()V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 46: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getDeviceId(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 47: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getSource(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 48: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final void setSource(int); + Signature: (I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 49: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 source I + + public final int getAction(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 50: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getActionMasked(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 51: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getActionIndex(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 52: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getFlags(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 53: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final long getDownTime(); + Signature: ()J + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 54: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final long getEventTime(); + Signature: ()J + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 55: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getX(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 56: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getY(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 57: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getPressure(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 58: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getSize(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 59: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getTouchMajor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 60: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getTouchMinor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 61: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getToolMajor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 62: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getToolMinor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 63: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getOrientation(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 64: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getAxisValue(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 65: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + + public final int getPointerCount(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 66: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getPointerId(int); + Signature: (I)I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 67: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final int getToolType(int); + Signature: (I)I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 68: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final int findPointerIndex(int); + Signature: (I)I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 69: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerId I + + public final float getX(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 70: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getY(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 71: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getPressure(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 72: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getSize(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 73: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getTouchMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 74: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getTouchMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 75: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getToolMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 76: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getToolMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 77: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getOrientation(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 78: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getAxisValue(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 79: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + + public final void getPointerCoords(int, android.view.MotionEvent$PointerCoords); + Signature: (ILandroid/view/MotionEvent$PointerCoords;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 80: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + public final void getPointerProperties(int, android.view.MotionEvent$PointerProperties); + Signature: (ILandroid/view/MotionEvent$PointerProperties;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 81: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerProperties Landroid/view/MotionEvent$PointerProperties; + + public final int getMetaState(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 82: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getButtonState(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 83: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getRawX(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 84: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getRawY(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 85: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getXPrecision(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 86: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getYPrecision(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 87: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getHistorySize(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 88: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final long getHistoricalEventTime(int); + Signature: (I)J + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 89: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalX(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 90: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalY(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 91: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalPressure(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 92: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalSize(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 93: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalTouchMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 94: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalTouchMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 95: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalToolMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 96: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalToolMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 97: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalOrientation(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 98: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalAxisValue(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 99: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pos I + + public final float getHistoricalX(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 100: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalY(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 101: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalPressure(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 102: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalSize(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 103: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalTouchMajor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 104: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalTouchMinor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 105: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalToolMajor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 106: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalToolMinor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 107: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalOrientation(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 108: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalAxisValue(int, int, int); + Signature: (III)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=4, args_size=4 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 109: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + 0 10 3 pos I + + public final void getHistoricalPointerCoords(int, int, android.view.MotionEvent$PointerCoords); + Signature: (IILandroid/view/MotionEvent$PointerCoords;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=4, args_size=4 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 110: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + 0 10 3 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + public final int getEdgeFlags(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 111: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final void setEdgeFlags(int); + Signature: (I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 112: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 flags I + + public final void setAction(int); + Signature: (I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 113: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 action I + + public final void offsetLocation(float, float); + Signature: (FF)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 114: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 deltaX F + 0 10 2 deltaY F + + public final void setLocation(float, float); + Signature: (FF)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 115: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 x F + 0 10 2 y F + + public final void transform(android.graphics.Matrix); + Signature: (Landroid/graphics/Matrix;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 116: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 matrix Landroid/graphics/Matrix; + + public final void addBatch(long, float, float, float, float, int); + Signature: (JFFFFI)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=8, args_size=7 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 117: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 x F + 0 10 4 y F + 0 10 5 pressure F + 0 10 6 size F + 0 10 7 metaState I + + public final void addBatch(long, android.view.MotionEvent$PointerCoords[], int); + Signature: (J[Landroid/view/MotionEvent$PointerCoords;I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=5, args_size=4 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 118: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 4 metaState I + + public java.lang.String toString(); + Signature: ()Ljava/lang/String; + flags: ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 119: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public static java.lang.String actionToString(int); + Signature: (I)Ljava/lang/String; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 120: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 action I + + public static java.lang.String axisToString(int); + Signature: (I)Ljava/lang/String; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 121: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 axis I + + public static int axisFromString(java.lang.String); + Signature: (Ljava/lang/String;)I + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 122: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 symbolicName Ljava/lang/String; + + public void writeToParcel(android.os.Parcel, int); + Signature: (Landroid/os/Parcel;I)V + flags: ACC_PUBLIC + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 123: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 out Landroid/os/Parcel; + 0 10 2 flags I + + static {}; + Signature: ()V + flags: ACC_STATIC + Code: + stack=1, locals=0, args_size=0 + 0: aconst_null + 1: putstatic #5 // Field CREATOR:Landroid/os/Parcelable$Creator; + 4: return + LineNumberTable: + line 213: 0 +} diff --git a/base/android/jni_generator/testMultipleJNIAdditionalImport.golden b/base/android/jni_generator/testMultipleJNIAdditionalImport.golden new file mode 100644 index 0000000000000000000000000000000000000000..0eecb5aeb1b0bd32eab2a946a2bf2091a15cc7d3 --- /dev/null +++ b/base/android/jni_generator/testMultipleJNIAdditionalImport.golden @@ -0,0 +1,95 @@ +// Copyright 2014 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/foo/Foo + +#ifndef org_chromium_foo_Foo_JNI +#define org_chromium_foo_Foo_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kFooClassPath[] = "org/chromium/foo/Foo"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_Foo_clazz __attribute__((unused)) = 0; +#define Foo_clazz(env) base::android::LazyGetClass(env, kFooClassPath, &g_Foo_clazz) + +} // namespace + +// Step 2: method stubs. + +static void DoSomething(JNIEnv* env, const base::android::JavaParamRef& + jcaller, + const base::android::JavaParamRef& callback1, + const base::android::JavaParamRef& callback2); + +JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething(JNIEnv* + env, jclass jcaller, + jobject callback1, + jobject callback2) { + return DoSomething(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, callback1), + base::android::JavaParamRef(env, callback2)); +} + +static base::subtle::AtomicWord g_Foo_calledByNative = 0; +static void Java_Foo_calledByNative(JNIEnv* env, const + base::android::JavaRefOrBare& callback1, + const base::android::JavaRefOrBare& callback2) { + CHECK_CLAZZ(env, Foo_clazz(env), + Foo_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, Foo_clazz(env), + "calledByNative", +"(" +"Lorg/chromium/foo/Bar1$Callback;" +"Lorg/chromium/foo/Bar2$Callback;" +")" +"V", + &g_Foo_calledByNative); + + env->CallStaticVoidMethod(Foo_clazz(env), + method_id, callback1.obj(), callback2.obj()); + jni_generator::CheckException(env); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsFoo[] = { + { "nativeDoSomething", +"(" +"Lorg/chromium/foo/Bar1$Callback;" +"Lorg/chromium/foo/Bar2$Callback;" +")" +"V", reinterpret_cast(Java_org_chromium_foo_Foo_nativeDoSomething) }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsFooSize = arraysize(kMethodsFoo); + + if (env->RegisterNatives(Foo_clazz(env), + kMethodsFoo, + kMethodsFooSize) < 0) { + jni_generator::HandleRegistrationError( + env, Foo_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_foo_Foo_JNI diff --git a/base/android/jni_generator/testNatives.golden b/base/android/jni_generator/testNatives.golden new file mode 100644 index 0000000000000000000000000000000000000000..3362c92841a643b4c5dd6843b9c5677f556fcd08 --- /dev/null +++ b/base/android/jni_generator/testNatives.golden @@ -0,0 +1,340 @@ +// Copyright 2014 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) + +} // namespace + +// Step 2: method stubs. + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit(JNIEnv* env, + jobject jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy(JNIEnv* env, + jobject jcaller, + jint nativeChromeBrowserProvider) { + ChromeBrowserProvider* native = + reinterpret_cast(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "Destroy"); + return native->Destroy(env, base::android::JavaParamRef(env, + jcaller)); +} + +JNI_GENERATOR_EXPORT jlong Java_org_chromium_TestJni_nativeAddBookmark(JNIEnv* + env, jobject jcaller, + jint nativeChromeBrowserProvider, + jstring url, + jstring title, + jboolean isFolder, + jlong parentId) { + ChromeBrowserProvider* native = + reinterpret_cast(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "AddBookmark", 0); + return native->AddBookmark(env, base::android::JavaParamRef(env, + jcaller), base::android::JavaParamRef(env, url), + base::android::JavaParamRef(env, title), isFolder, parentId); +} + +static base::android::ScopedJavaLocalRef GetDomainAndRegistry(JNIEnv* + env, const base::android::JavaParamRef& jcaller, + const base::android::JavaParamRef& url); + +JNI_GENERATOR_EXPORT jstring + Java_org_chromium_TestJni_nativeGetDomainAndRegistry(JNIEnv* env, jclass + jcaller, + jstring url) { + return GetDomainAndRegistry(env, base::android::JavaParamRef(env, + jcaller), base::android::JavaParamRef(env, url)).Release(); +} + +static void CreateHistoricalTabFromState(JNIEnv* env, const + base::android::JavaParamRef& jcaller, + const base::android::JavaParamRef& state, + jint tab_index); + +JNI_GENERATOR_EXPORT void + Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState(JNIEnv* env, + jclass jcaller, + jbyteArray state, + jint tab_index) { + return CreateHistoricalTabFromState(env, + base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, state), tab_index); +} + +static base::android::ScopedJavaLocalRef GetStateAsByteArray(JNIEnv* + env, const base::android::JavaParamRef& jcaller, + const base::android::JavaParamRef& view); + +JNI_GENERATOR_EXPORT jbyteArray + Java_org_chromium_TestJni_nativeGetStateAsByteArray(JNIEnv* env, jobject + jcaller, + jobject view) { + return GetStateAsByteArray(env, base::android::JavaParamRef(env, + jcaller), base::android::JavaParamRef(env, view)).Release(); +} + +static base::android::ScopedJavaLocalRef + GetAutofillProfileGUIDs(JNIEnv* env, const + base::android::JavaParamRef& jcaller); + +JNI_GENERATOR_EXPORT jobjectArray + Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs(JNIEnv* env, jclass + jcaller) { + return GetAutofillProfileGUIDs(env, base::android::JavaParamRef(env, + jcaller)).Release(); +} + +static void SetRecognitionResults(JNIEnv* env, const + base::android::JavaParamRef& jcaller, + jint sessionId, + const base::android::JavaParamRef& results); + +JNI_GENERATOR_EXPORT void + Java_org_chromium_TestJni_nativeSetRecognitionResults(JNIEnv* env, jobject + jcaller, + jint sessionId, + jobjectArray results) { + return SetRecognitionResults(env, base::android::JavaParamRef(env, + jcaller), sessionId, base::android::JavaParamRef(env, + results)); +} + +JNI_GENERATOR_EXPORT jlong + Java_org_chromium_TestJni_nativeAddBookmarkFromAPI(JNIEnv* env, jobject + jcaller, + jint nativeChromeBrowserProvider, + jstring url, + jobject created, + jobject isBookmark, + jobject date, + jbyteArray favicon, + jstring title, + jobject visits) { + ChromeBrowserProvider* native = + reinterpret_cast(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "AddBookmarkFromAPI", 0); + return native->AddBookmarkFromAPI(env, + base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, url), + base::android::JavaParamRef(env, created), + base::android::JavaParamRef(env, isBookmark), + base::android::JavaParamRef(env, date), + base::android::JavaParamRef(env, favicon), + base::android::JavaParamRef(env, title), + base::android::JavaParamRef(env, visits)); +} + +static jint FindAll(JNIEnv* env, const base::android::JavaParamRef& + jcaller, + const base::android::JavaParamRef& find); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeFindAll(JNIEnv* env, + jobject jcaller, + jstring find) { + return FindAll(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, find)); +} + +static base::android::ScopedJavaLocalRef GetInnerClass(JNIEnv* env, + const base::android::JavaParamRef& jcaller); + +JNI_GENERATOR_EXPORT jobject + Java_org_chromium_TestJni_nativeGetInnerClass(JNIEnv* env, jclass jcaller) { + return GetInnerClass(env, base::android::JavaParamRef(env, + jcaller)).Release(); +} + +JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeQueryBitmap(JNIEnv* + env, jobject jcaller, + jint nativeChromeBrowserProvider, + jobjectArray projection, + jstring selection, + jobjectArray selectionArgs, + jstring sortOrder) { + ChromeBrowserProvider* native = + reinterpret_cast(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "QueryBitmap", NULL); + return native->QueryBitmap(env, base::android::JavaParamRef(env, + jcaller), base::android::JavaParamRef(env, projection), + base::android::JavaParamRef(env, selection), + base::android::JavaParamRef(env, selectionArgs), + base::android::JavaParamRef(env, sortOrder)).Release(); +} + +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeGotOrientation(JNIEnv* + env, jobject jcaller, + jint nativeDataFetcherImplAndroid, + jdouble alpha, + jdouble beta, + jdouble gamma) { + DataFetcherImplAndroid* native = + reinterpret_cast(nativeDataFetcherImplAndroid); + CHECK_NATIVE_PTR(env, jcaller, native, "GotOrientation"); + return native->GotOrientation(env, base::android::JavaParamRef(env, + jcaller), alpha, beta, gamma); +} + +static base::android::ScopedJavaLocalRef + MessWithJavaException(JNIEnv* env, const + base::android::JavaParamRef& jcaller, + const base::android::JavaParamRef& e); + +JNI_GENERATOR_EXPORT jthrowable + Java_org_chromium_TestJni_nativeMessWithJavaException(JNIEnv* env, jclass + jcaller, + jthrowable e) { + return MessWithJavaException(env, base::android::JavaParamRef(env, + jcaller), base::android::JavaParamRef(env, e)).Release(); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsTestJni[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast(Java_org_chromium_TestJni_nativeInit) }, + { "nativeDestroy", +"(" +"I" +")" +"V", reinterpret_cast(Java_org_chromium_TestJni_nativeDestroy) }, + { "nativeAddBookmark", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Z" +"J" +")" +"J", reinterpret_cast(Java_org_chromium_TestJni_nativeAddBookmark) }, + { "nativeGetDomainAndRegistry", +"(" +"Ljava/lang/String;" +")" +"Ljava/lang/String;", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetDomainAndRegistry) + }, + { "nativeCreateHistoricalTabFromState", +"(" +"[B" +"I" +")" +"V", + reinterpret_cast(Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState) + }, + { "nativeGetStateAsByteArray", +"(" +"Landroid/view/View;" +")" +"[B", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetStateAsByteArray) + }, + { "nativeGetAutofillProfileGUIDs", +"(" +")" +"[Ljava/lang/String;", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs) + }, + { "nativeSetRecognitionResults", +"(" +"I" +"[Ljava/lang/String;" +")" +"V", + reinterpret_cast(Java_org_chromium_TestJni_nativeSetRecognitionResults) + }, + { "nativeAddBookmarkFromAPI", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/Long;" +"Ljava/lang/Boolean;" +"Ljava/lang/Long;" +"[B" +"Ljava/lang/String;" +"Ljava/lang/Integer;" +")" +"J", reinterpret_cast(Java_org_chromium_TestJni_nativeAddBookmarkFromAPI) + }, + { "nativeFindAll", +"(" +"Ljava/lang/String;" +")" +"I", reinterpret_cast(Java_org_chromium_TestJni_nativeFindAll) }, + { "nativeGetInnerClass", +"(" +")" +"Lorg/chromium/example/jni_generator/SampleForTests$OnFrameAvailableListener;", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetInnerClass) }, + { "nativeQueryBitmap", +"(" +"I" +"[Ljava/lang/String;" +"Ljava/lang/String;" +"[Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Landroid/graphics/Bitmap;", + reinterpret_cast(Java_org_chromium_TestJni_nativeQueryBitmap) }, + { "nativeGotOrientation", +"(" +"I" +"D" +"D" +"D" +")" +"V", reinterpret_cast(Java_org_chromium_TestJni_nativeGotOrientation) }, + { "nativeMessWithJavaException", +"(" +"Ljava/lang/Throwable;" +")" +"Ljava/lang/Throwable;", + reinterpret_cast(Java_org_chromium_TestJni_nativeMessWithJavaException) + }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsTestJniSize = arraysize(kMethodsTestJni); + + if (env->RegisterNatives(TestJni_clazz(env), + kMethodsTestJni, + kMethodsTestJniSize) < 0) { + jni_generator::HandleRegistrationError( + env, TestJni_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testNativesLong.golden b/base/android/jni_generator/testNativesLong.golden new file mode 100644 index 0000000000000000000000000000000000000000..ec029ce306ba0edc82e1fe0cfbac2ace7b1662cb --- /dev/null +++ b/base/android/jni_generator/testNativesLong.golden @@ -0,0 +1,66 @@ +// Copyright 2014 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) + +} // namespace + +// Step 2: method stubs. +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy(JNIEnv* env, + jobject jcaller, + jlong nativeChromeBrowserProvider) { + ChromeBrowserProvider* native = + reinterpret_cast(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "Destroy"); + return native->Destroy(env, base::android::JavaParamRef(env, + jcaller)); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsTestJni[] = { + { "nativeDestroy", +"(" +"J" +")" +"V", reinterpret_cast(Java_org_chromium_TestJni_nativeDestroy) }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsTestJniSize = arraysize(kMethodsTestJni); + + if (env->RegisterNatives(TestJni_clazz(env), + kMethodsTestJni, + kMethodsTestJniSize) < 0) { + jni_generator::HandleRegistrationError( + env, TestJni_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testSingleJNIAdditionalImport.golden b/base/android/jni_generator/testSingleJNIAdditionalImport.golden new file mode 100644 index 0000000000000000000000000000000000000000..ef618da80a65664eeab11610b99d9f7871d9f472 --- /dev/null +++ b/base/android/jni_generator/testSingleJNIAdditionalImport.golden @@ -0,0 +1,89 @@ +// Copyright 2014 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/foo/Foo + +#ifndef org_chromium_foo_Foo_JNI +#define org_chromium_foo_Foo_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kFooClassPath[] = "org/chromium/foo/Foo"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_Foo_clazz __attribute__((unused)) = 0; +#define Foo_clazz(env) base::android::LazyGetClass(env, kFooClassPath, &g_Foo_clazz) + +} // namespace + +// Step 2: method stubs. + +static void DoSomething(JNIEnv* env, const base::android::JavaParamRef& + jcaller, + const base::android::JavaParamRef& callback); + +JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething(JNIEnv* + env, jclass jcaller, + jobject callback) { + return DoSomething(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, callback)); +} + +static base::subtle::AtomicWord g_Foo_calledByNative = 0; +static void Java_Foo_calledByNative(JNIEnv* env, const + base::android::JavaRefOrBare& callback) { + CHECK_CLAZZ(env, Foo_clazz(env), + Foo_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, Foo_clazz(env), + "calledByNative", +"(" +"Lorg/chromium/foo/Bar$Callback;" +")" +"V", + &g_Foo_calledByNative); + + env->CallStaticVoidMethod(Foo_clazz(env), + method_id, callback.obj()); + jni_generator::CheckException(env); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsFoo[] = { + { "nativeDoSomething", +"(" +"Lorg/chromium/foo/Bar$Callback;" +")" +"V", reinterpret_cast(Java_org_chromium_foo_Foo_nativeDoSomething) }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsFooSize = arraysize(kMethodsFoo); + + if (env->RegisterNatives(Foo_clazz(env), + kMethodsFoo, + kMethodsFooSize) < 0) { + jni_generator::HandleRegistrationError( + env, Foo_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_foo_Foo_JNI diff --git a/base/android/jni_int_wrapper.h b/base/android/jni_int_wrapper.h new file mode 100644 index 0000000000000000000000000000000000000000..fa0f3d5d54483e4ee07185f00bd2f7a3f448e1ec --- /dev/null +++ b/base/android/jni_int_wrapper.h @@ -0,0 +1,56 @@ +// Copyright 2014 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 BASE_ANDROID_JNI_INT_WRAPPER_H_ +#define BASE_ANDROID_JNI_INT_WRAPPER_H_ + +// Wrapper used to receive int when calling Java from native. +// The wrapper disallows automatic conversion of long to int. +// This is to avoid a common anti-pattern where a Java int is used +// to receive a native pointer. Please use a Java long to receive +// native pointers, so that the code works on both 32-bit and 64-bit +// platforms. Note the wrapper allows other lossy conversions into +// jint that could be consider anti-patterns, such as from size_t. + +// Checking is only done in debugging builds. + +#ifdef NDEBUG + +typedef jint JniIntWrapper; + +// This inline is sufficiently trivial that it does not change the +// final code generated by g++. +inline jint as_jint(JniIntWrapper wrapper) { + return wrapper; +} + +#else + +class JniIntWrapper { + public: + JniIntWrapper() : i_(0) {} + JniIntWrapper(int i) : i_(i) {} + JniIntWrapper(const JniIntWrapper& ji) : i_(ji.i_) {} + template JniIntWrapper(const T& t) : i_(t) {} + jint as_jint() const { return i_; } + private: + // If you get an "is private" error at the line below it is because you used + // an implicit conversion to convert a long to an int when calling Java. + // We disallow this, as a common anti-pattern allows converting a native + // pointer (intptr_t) to a Java int. Please use a Java long to represent + // a native pointer. If you want a lossy conversion, please use an + // explicit conversion in your C++ code. Note an error is only seen when + // compiling on a 64-bit platform, as intptr_t is indistinguishable from + // int on 32-bit platforms. + JniIntWrapper(long); + jint i_; +}; + +inline jint as_jint(const JniIntWrapper& wrapper) { + return wrapper.as_jint(); +} + +#endif // NDEBUG + +#endif // BASE_ANDROID_JNI_INT_WRAPPER_H_ diff --git a/base/android/jni_string.cc b/base/android/jni_string.cc new file mode 100644 index 0000000000000000000000000000000000000000..f28f6498ece95f400f2a34981030e4693dfac22a --- /dev/null +++ b/base/android/jni_string.cc @@ -0,0 +1,121 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_string.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" + +namespace { + +// Internal version that does not use a scoped local pointer. +jstring ConvertUTF16ToJavaStringImpl(JNIEnv* env, + const base::StringPiece16& str) { + jstring result = env->NewString(str.data(), str.length()); + base::android::CheckException(env); + return result; +} + +} // namespace + +namespace base { +namespace android { + +void ConvertJavaStringToUTF8(JNIEnv* env, jstring str, std::string* result) { + DCHECK(str); + if (!str) { + LOG(WARNING) << "ConvertJavaStringToUTF8 called with null string."; + result->clear(); + return; + } + const jsize length = env->GetStringLength(str); + if (!length) { + result->clear(); + CheckException(env); + return; + } + // JNI's GetStringUTFChars() returns strings in Java "modified" UTF8, so + // instead get the String in UTF16 and convert using chromium's conversion + // function that yields plain (non Java-modified) UTF8. + const jchar* chars = env->GetStringChars(str, NULL); + DCHECK(chars); + UTF16ToUTF8(chars, length, result); + env->ReleaseStringChars(str, chars); + CheckException(env); +} + +std::string ConvertJavaStringToUTF8(JNIEnv* env, jstring str) { + std::string result; + ConvertJavaStringToUTF8(env, str, &result); + return result; +} + +std::string ConvertJavaStringToUTF8(const JavaRef& str) { + return ConvertJavaStringToUTF8(AttachCurrentThread(), str.obj()); +} + +std::string ConvertJavaStringToUTF8(JNIEnv* env, const JavaRef& str) { + return ConvertJavaStringToUTF8(env, str.obj()); +} + +ScopedJavaLocalRef ConvertUTF8ToJavaString( + JNIEnv* env, + const base::StringPiece& str) { + // JNI's NewStringUTF expects "modified" UTF8 so instead create the string + // via our own UTF16 conversion utility. + // Further, Dalvik requires the string passed into NewStringUTF() to come from + // a trusted source. We can't guarantee that all UTF8 will be sanitized before + // it gets here, so constructing via UTF16 side-steps this issue. + // (Dalvik stores strings internally as UTF16 anyway, so there shouldn't be + // a significant performance hit by doing it this way). + return ScopedJavaLocalRef(env, ConvertUTF16ToJavaStringImpl( + env, UTF8ToUTF16(str))); +} + +void ConvertJavaStringToUTF16(JNIEnv* env, jstring str, string16* result) { + DCHECK(str); + if (!str) { + LOG(WARNING) << "ConvertJavaStringToUTF16 called with null string."; + result->clear(); + return; + } + const jsize length = env->GetStringLength(str); + if (!length) { + result->clear(); + CheckException(env); + return; + } + const jchar* chars = env->GetStringChars(str, NULL); + DCHECK(chars); + // GetStringChars isn't required to NULL-terminate the strings + // it returns, so the length must be explicitly checked. + result->assign(chars, length); + env->ReleaseStringChars(str, chars); + CheckException(env); +} + +string16 ConvertJavaStringToUTF16(JNIEnv* env, jstring str) { + string16 result; + ConvertJavaStringToUTF16(env, str, &result); + return result; +} + +string16 ConvertJavaStringToUTF16(const JavaRef& str) { + return ConvertJavaStringToUTF16(AttachCurrentThread(), str.obj()); +} + +string16 ConvertJavaStringToUTF16(JNIEnv* env, const JavaRef& str) { + return ConvertJavaStringToUTF16(env, str.obj()); +} + +ScopedJavaLocalRef ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str) { + return ScopedJavaLocalRef(env, + ConvertUTF16ToJavaStringImpl(env, str)); +} + +} // namespace android +} // namespace base diff --git a/base/android/jni_string.h b/base/android/jni_string.h new file mode 100644 index 0000000000000000000000000000000000000000..09e85f306f0c7eae9e3c33445af0cfbf6eadf4cf --- /dev/null +++ b/base/android/jni_string.h @@ -0,0 +1,49 @@ +// Copyright (c) 2012 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 BASE_ANDROID_JNI_STRING_H_ +#define BASE_ANDROID_JNI_STRING_H_ + +#include +#include + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" +#include "base/strings/string_piece.h" + +namespace base { +namespace android { + +// Convert a Java string to UTF8. Returns a std string. +BASE_EXPORT void ConvertJavaStringToUTF8(JNIEnv* env, + jstring str, + std::string* result); +BASE_EXPORT std::string ConvertJavaStringToUTF8(JNIEnv* env, jstring str); +BASE_EXPORT std::string ConvertJavaStringToUTF8(const JavaRef& str); +BASE_EXPORT std::string ConvertJavaStringToUTF8(JNIEnv* env, + const JavaRef& str); + +// Convert a std string to Java string. +BASE_EXPORT ScopedJavaLocalRef ConvertUTF8ToJavaString( + JNIEnv* env, + const base::StringPiece& str); + +// Convert a Java string to UTF16. Returns a string16. +BASE_EXPORT void ConvertJavaStringToUTF16(JNIEnv* env, + jstring str, + string16* result); +BASE_EXPORT string16 ConvertJavaStringToUTF16(JNIEnv* env, jstring str); +BASE_EXPORT string16 ConvertJavaStringToUTF16(const JavaRef& str); +BASE_EXPORT string16 ConvertJavaStringToUTF16(JNIEnv* env, + const JavaRef& str); + +// Convert a string16 to a Java string. +BASE_EXPORT ScopedJavaLocalRef ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_STRING_H_ diff --git a/base/android/jni_string_unittest.cc b/base/android/jni_string_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..3da8b870e6649bfc4140ef2881fe21a30c205471 --- /dev/null +++ b/base/android/jni_string_unittest.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_string.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +TEST(JniString, BasicConversionsUTF8) { + const std::string kSimpleString = "SimpleTest8"; + JNIEnv* env = AttachCurrentThread(); + std::string result = + ConvertJavaStringToUTF8(ConvertUTF8ToJavaString(env, kSimpleString)); + EXPECT_EQ(kSimpleString, result); +} + +TEST(JniString, BasicConversionsUTF16) { + const string16 kSimpleString = UTF8ToUTF16("SimpleTest16"); + JNIEnv* env = AttachCurrentThread(); + string16 result = + ConvertJavaStringToUTF16(ConvertUTF16ToJavaString(env, kSimpleString)); + EXPECT_EQ(kSimpleString, result); +} + +TEST(JniString, EmptyConversionUTF8) { + const std::string kEmptyString = ""; + JNIEnv* env = AttachCurrentThread(); + std::string result = + ConvertJavaStringToUTF8(ConvertUTF8ToJavaString(env, kEmptyString)); + EXPECT_EQ(kEmptyString, result); +} + +TEST(JniString, EmptyConversionUTF16) { + const string16 kEmptyString = UTF8ToUTF16(""); + JNIEnv* env = AttachCurrentThread(); + string16 result = + ConvertJavaStringToUTF16(ConvertUTF16ToJavaString(env, kEmptyString)); + EXPECT_EQ(kEmptyString, result); +} + +} // namespace android +} // namespace base diff --git a/base/android/scoped_java_ref.cc b/base/android/scoped_java_ref.cc new file mode 100644 index 0000000000000000000000000000000000000000..7d31a75bc8f1fa933d3a2e25f99b110c904619ef --- /dev/null +++ b/base/android/scoped_java_ref.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2012 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. + +#include "base/android/scoped_java_ref.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" + +namespace base { +namespace android { +namespace { + +const int kDefaultLocalFrameCapacity = 16; + +} // namespace + +ScopedJavaLocalFrame::ScopedJavaLocalFrame(JNIEnv* env) : env_(env) { + int failed = env_->PushLocalFrame(kDefaultLocalFrameCapacity); + DCHECK(!failed); +} + +ScopedJavaLocalFrame::ScopedJavaLocalFrame(JNIEnv* env, int capacity) + : env_(env) { + int failed = env_->PushLocalFrame(capacity); + DCHECK(!failed); +} + +ScopedJavaLocalFrame::~ScopedJavaLocalFrame() { + env_->PopLocalFrame(nullptr); +} + +#if DCHECK_IS_ON() +// This constructor is inlined when DCHECKs are disabled; don't add anything +// else here. +JavaRef::JavaRef(JNIEnv* env, jobject obj) : obj_(obj) { + if (obj) { + DCHECK(env && env->GetObjectRefType(obj) == JNILocalRefType); + } +} +#endif + +JNIEnv* JavaRef::SetNewLocalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewLocalRef(obj); + if (obj_) + env->DeleteLocalRef(obj_); + obj_ = obj; + return env; +} + +void JavaRef::SetNewGlobalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewGlobalRef(obj); + if (obj_) + env->DeleteGlobalRef(obj_); + obj_ = obj; +} + +void JavaRef::ResetLocalRef(JNIEnv* env) { + if (obj_) { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + env->DeleteLocalRef(obj_); + obj_ = nullptr; + } +} + +void JavaRef::ResetGlobalRef() { + if (obj_) { + AttachCurrentThread()->DeleteGlobalRef(obj_); + obj_ = nullptr; + } +} + +jobject JavaRef::ReleaseInternal() { + jobject obj = obj_; + obj_ = nullptr; + return obj; +} + +} // namespace android +} // namespace base diff --git a/base/android/scoped_java_ref.h b/base/android/scoped_java_ref.h new file mode 100644 index 0000000000000000000000000000000000000000..6d728e969556216914f6713db765d36627eeddd3 --- /dev/null +++ b/base/android/scoped_java_ref.h @@ -0,0 +1,301 @@ +// Copyright (c) 2012 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 BASE_ANDROID_SCOPED_JAVA_REF_H_ +#define BASE_ANDROID_SCOPED_JAVA_REF_H_ + +#include +#include + +#include +#include + +#include "base/base_export.h" +#include "base/logging.h" +#include "base/macros.h" + +namespace base { +namespace android { + +// Creates a new local reference frame, in which at least a given number of +// local references can be created. Note that local references already created +// in previous local frames are still valid in the current local frame. +class BASE_EXPORT ScopedJavaLocalFrame { + public: + explicit ScopedJavaLocalFrame(JNIEnv* env); + ScopedJavaLocalFrame(JNIEnv* env, int capacity); + ~ScopedJavaLocalFrame(); + + private: + // This class is only good for use on the thread it was created on so + // it's safe to cache the non-threadsafe JNIEnv* inside this object. + JNIEnv* env_; + + DISALLOW_COPY_AND_ASSIGN(ScopedJavaLocalFrame); +}; + +// Forward declare the generic java reference template class. +template class JavaRef; + +// Template specialization of JavaRef, which acts as the base class for all +// other JavaRef<> template types. This allows you to e.g. pass +// ScopedJavaLocalRef into a function taking const JavaRef& +template<> +class BASE_EXPORT JavaRef { + public: + // Initializes a null reference. Don't add anything else here; it's inlined. + JavaRef() : obj_(nullptr) {} + + // Allow nullptr to be converted to JavaRef. This avoids having to declare an + // empty JavaRef just to pass null to a function, and makes C++ "nullptr" and + // Java "null" equivalent. + JavaRef(std::nullptr_t) : JavaRef() {} + + // Public to allow destruction of null JavaRef objects. + // Don't add anything else here; it's inlined. + ~JavaRef() {} + + jobject obj() const { return obj_; } + + bool is_null() const { return obj_ == nullptr; } + + protected: + // Takes ownership of the |obj| reference passed; requires it to be a local + // reference type. +#if DCHECK_IS_ON() + // Implementation contains a DCHECK; implement out-of-line when DCHECK_IS_ON. + JavaRef(JNIEnv* env, jobject obj); +#else + // Don't add anything else here; it's inlined. + JavaRef(JNIEnv* env, jobject obj) : obj_(obj) {} +#endif + + void swap(JavaRef& other) { std::swap(obj_, other.obj_); } + + // The following are implementation detail convenience methods, for + // use by the sub-classes. + JNIEnv* SetNewLocalRef(JNIEnv* env, jobject obj); + void SetNewGlobalRef(JNIEnv* env, jobject obj); + void ResetLocalRef(JNIEnv* env); + void ResetGlobalRef(); + jobject ReleaseInternal(); + + private: + jobject obj_; + + DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Generic base class for ScopedJavaLocalRef and ScopedJavaGlobalRef. Useful +// for allowing functions to accept a reference without having to mandate +// whether it is a local or global type. +template +class JavaRef : public JavaRef { + public: + JavaRef() {} + JavaRef(std::nullptr_t) : JavaRef(nullptr) {} + ~JavaRef() {} + + T obj() const { return static_cast(JavaRef::obj()); } + + protected: + JavaRef(JNIEnv* env, T obj) : JavaRef(env, obj) {} + + private: + DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Holds a local reference to a JNI method parameter. +// Method parameters should not be deleted, and so this class exists purely to +// wrap them as a JavaRef in the JNI binding generator. Do not create +// instances manually. +template +class JavaParamRef : public JavaRef { + public: + // Assumes that |obj| is a parameter passed to a JNI method from Java. + // Does not assume ownership as parameters should not be deleted. + JavaParamRef(JNIEnv* env, T obj) : JavaRef(env, obj) {} + + // Allow nullptr to be converted to JavaParamRef. Some unit tests call JNI + // methods directly from C++ and pass null for objects which are not actually + // used by the implementation (e.g. the caller object); allow this to keep + // working. + JavaParamRef(std::nullptr_t) : JavaRef(nullptr) {} + + ~JavaParamRef() {} + + // TODO(torne): remove this cast once we're using JavaRef consistently. + // http://crbug.com/506850 + operator T() const { return JavaRef::obj(); } + + private: + DISALLOW_COPY_AND_ASSIGN(JavaParamRef); +}; + +// Holds a local reference to a Java object. The local reference is scoped +// to the lifetime of this object. +// Instances of this class may hold onto any JNIEnv passed into it until +// destroyed. Therefore, since a JNIEnv is only suitable for use on a single +// thread, objects of this class must be created, used, and destroyed, on a +// single thread. +// Therefore, this class should only be used as a stack-based object and from a +// single thread. If you wish to have the reference outlive the current +// callstack (e.g. as a class member) or you wish to pass it across threads, +// use a ScopedJavaGlobalRef instead. +template +class ScopedJavaLocalRef : public JavaRef { + public: + ScopedJavaLocalRef() : env_(nullptr) {} + ScopedJavaLocalRef(std::nullptr_t) : env_(nullptr) {} + + // Non-explicit copy constructor, to allow ScopedJavaLocalRef to be returned + // by value as this is the normal usage pattern. + ScopedJavaLocalRef(const ScopedJavaLocalRef& other) + : env_(other.env_) { + this->SetNewLocalRef(env_, other.obj()); + } + + ScopedJavaLocalRef(ScopedJavaLocalRef&& other) : env_(other.env_) { + this->swap(other); + } + + explicit ScopedJavaLocalRef(const JavaRef& other) : env_(nullptr) { + this->Reset(other); + } + + // Assumes that |obj| is a local reference to a Java object and takes + // ownership of this local reference. + // TODO(torne): this shouldn't be used outside of JNI helper functions but + // there are currently some cases where there aren't helpers for things. + ScopedJavaLocalRef(JNIEnv* env, T obj) : JavaRef(env, obj), env_(env) {} + + ~ScopedJavaLocalRef() { + this->Reset(); + } + + // Overloaded assignment operator defined for consistency with the implicit + // copy constructor. + void operator=(const ScopedJavaLocalRef& other) { + this->Reset(other); + } + + void operator=(ScopedJavaLocalRef&& other) { + env_ = other.env_; + this->swap(other); + } + + void Reset() { + this->ResetLocalRef(env_); + } + + void Reset(const ScopedJavaLocalRef& other) { + // We can copy over env_ here as |other| instance must be from the same + // thread as |this| local ref. (See class comment for multi-threading + // limitations, and alternatives). + this->Reset(other.env_, other.obj()); + } + + void Reset(const JavaRef& other) { + // If |env_| was not yet set (is still null) it will be attached to the + // current thread in SetNewLocalRef(). + this->Reset(env_, other.obj()); + } + + // Creates a new local reference to the Java object, unlike the constructor + // with the same parameters that takes ownership of the existing reference. + // TODO(torne): these should match as this is confusing. + void Reset(JNIEnv* env, T obj) { env_ = this->SetNewLocalRef(env, obj); } + + // Releases the local reference to the caller. The caller *must* delete the + // local reference when it is done with it. Note that calling a Java method + // is *not* a transfer of ownership and Release() should not be used. + T Release() { + return static_cast(this->ReleaseInternal()); + } + + private: + // This class is only good for use on the thread it was created on so + // it's safe to cache the non-threadsafe JNIEnv* inside this object. + JNIEnv* env_; + + // Prevent ScopedJavaLocalRef(JNIEnv*, T obj) from being used to take + // ownership of a JavaParamRef's underlying object - parameters are not + // allowed to be deleted and so should not be owned by ScopedJavaLocalRef. + // TODO(torne): this can be removed once JavaParamRef no longer has an + // implicit conversion back to T. + ScopedJavaLocalRef(JNIEnv* env, const JavaParamRef& other); +}; + +// Holds a global reference to a Java object. The global reference is scoped +// to the lifetime of this object. This class does not hold onto any JNIEnv* +// passed to it, hence it is safe to use across threads (within the constraints +// imposed by the underlying Java object that it references). +template +class ScopedJavaGlobalRef : public JavaRef { + public: + ScopedJavaGlobalRef() {} + ScopedJavaGlobalRef(std::nullptr_t) {} + + ScopedJavaGlobalRef(const ScopedJavaGlobalRef& other) { + this->Reset(other); + } + + ScopedJavaGlobalRef(ScopedJavaGlobalRef&& other) { this->swap(other); } + + ScopedJavaGlobalRef(JNIEnv* env, T obj) { this->Reset(env, obj); } + + explicit ScopedJavaGlobalRef(const JavaRef& other) { this->Reset(other); } + + ~ScopedJavaGlobalRef() { + this->Reset(); + } + + // Overloaded assignment operator defined for consistency with the implicit + // copy constructor. + void operator=(const ScopedJavaGlobalRef& other) { + this->Reset(other); + } + + void operator=(ScopedJavaGlobalRef&& other) { this->swap(other); } + + void Reset() { + this->ResetGlobalRef(); + } + + void Reset(const JavaRef& other) { this->Reset(nullptr, other.obj()); } + + void Reset(JNIEnv* env, const JavaParamRef& other) { + this->Reset(env, other.obj()); + } + + void Reset(JNIEnv* env, T obj) { this->SetNewGlobalRef(env, obj); } + + // Releases the global reference to the caller. The caller *must* delete the + // global reference when it is done with it. Note that calling a Java method + // is *not* a transfer of ownership and Release() should not be used. + T Release() { + return static_cast(this->ReleaseInternal()); + } +}; + +// Temporary type for parameters to Java functions, to allow incremental +// migration from bare jobject to JavaRef. Don't use outside JNI generator. +template +class JavaRefOrBare { + public: + JavaRefOrBare(std::nullptr_t) : obj_(nullptr) {} + JavaRefOrBare(const JavaRef& ref) : obj_(ref.obj()) {} + JavaRefOrBare(T obj) : obj_(obj) {} + T obj() const { return obj_; } + + private: + T obj_; + + DISALLOW_COPY_AND_ASSIGN(JavaRefOrBare); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_SCOPED_JAVA_REF_H_ diff --git a/base/android/scoped_java_ref_unittest.cc b/base/android/scoped_java_ref_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..99d035bb77defe2db4fa5253c820e2923f389a9f --- /dev/null +++ b/base/android/scoped_java_ref_unittest.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2012 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. + +#include "base/android/scoped_java_ref.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { +int g_local_refs = 0; +int g_global_refs = 0; + +const JNINativeInterface* g_previous_functions; + +jobject NewGlobalRef(JNIEnv* env, jobject obj) { + ++g_global_refs; + return g_previous_functions->NewGlobalRef(env, obj); +} + +void DeleteGlobalRef(JNIEnv* env, jobject obj) { + --g_global_refs; + return g_previous_functions->DeleteGlobalRef(env, obj); +} + +jobject NewLocalRef(JNIEnv* env, jobject obj) { + ++g_local_refs; + return g_previous_functions->NewLocalRef(env, obj); +} + +void DeleteLocalRef(JNIEnv* env, jobject obj) { + --g_local_refs; + return g_previous_functions->DeleteLocalRef(env, obj); +} +} // namespace + +class ScopedJavaRefTest : public testing::Test { + protected: + void SetUp() override { + g_local_refs = 0; + g_global_refs = 0; + JNIEnv* env = AttachCurrentThread(); + g_previous_functions = env->functions; + hooked_functions = *g_previous_functions; + env->functions = &hooked_functions; + // We inject our own functions in JNINativeInterface so we can keep track + // of the reference counting ourselves. + hooked_functions.NewGlobalRef = &NewGlobalRef; + hooked_functions.DeleteGlobalRef = &DeleteGlobalRef; + hooked_functions.NewLocalRef = &NewLocalRef; + hooked_functions.DeleteLocalRef = &DeleteLocalRef; + } + + void TearDown() override { + JNIEnv* env = AttachCurrentThread(); + env->functions = g_previous_functions; + } + // From JellyBean release, the instance of this struct provided in JNIEnv is + // read-only, so we deep copy it to allow individual functions to be hooked. + JNINativeInterface hooked_functions; +}; + +// The main purpose of this is testing the various conversions compile. +TEST_F(ScopedJavaRefTest, Conversions) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef str = ConvertUTF8ToJavaString(env, "string"); + ScopedJavaGlobalRef global(str); + { + ScopedJavaGlobalRef global_obj(str); + ScopedJavaLocalRef local_obj(global); + const JavaRef& obj_ref1(str); + const JavaRef& obj_ref2(global); + EXPECT_TRUE(env->IsSameObject(obj_ref1.obj(), obj_ref2.obj())); + EXPECT_TRUE(env->IsSameObject(global_obj.obj(), obj_ref2.obj())); + } + global.Reset(str); + const JavaRef& str_ref = str; + EXPECT_EQ("string", ConvertJavaStringToUTF8(str_ref)); + str.Reset(); +} + +TEST_F(ScopedJavaRefTest, RefCounts) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef str; + // The ConvertJavaStringToUTF8 below creates a new string that would normally + // return a local ref. We simulate that by starting the g_local_refs count at + // 1. + g_local_refs = 1; + str.Reset(ConvertUTF8ToJavaString(env, "string")); + EXPECT_EQ(1, g_local_refs); + EXPECT_EQ(0, g_global_refs); + { + ScopedJavaGlobalRef global_str(str); + ScopedJavaGlobalRef global_obj(global_str); + EXPECT_EQ(1, g_local_refs); + EXPECT_EQ(2, g_global_refs); + + ScopedJavaLocalRef str2(env, str.Release()); + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef str3(str2); + EXPECT_EQ(2, g_local_refs); + } + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef str4((ScopedJavaLocalRef(str2))); + EXPECT_EQ(2, g_local_refs); + } + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef str5; + str5 = ScopedJavaLocalRef(str2); + EXPECT_EQ(2, g_local_refs); + } + EXPECT_EQ(1, g_local_refs); + str2.Reset(); + EXPECT_EQ(0, g_local_refs); + global_str.Reset(); + EXPECT_EQ(1, g_global_refs); + ScopedJavaGlobalRef global_obj2(global_obj); + EXPECT_EQ(2, g_global_refs); + } + + EXPECT_EQ(0, g_local_refs); + EXPECT_EQ(0, g_global_refs); +} + +} // namespace android +} // namespace base diff --git a/base/base_paths.cc b/base/base_paths.cc new file mode 100644 index 0000000000000000000000000000000000000000..31bc55401af4a8087b3319ba4f35d796e918c376 --- /dev/null +++ b/base/base_paths.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2006-2008 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. + +#include "base/base_paths.h" + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/path_service.h" + +namespace base { + +bool PathProvider(int key, FilePath* result) { + // NOTE: DIR_CURRENT is a special case in PathService::Get + + switch (key) { + case DIR_EXE: + PathService::Get(FILE_EXE, result); + *result = result->DirName(); + return true; + case DIR_MODULE: + PathService::Get(FILE_MODULE, result); + *result = result->DirName(); + return true; + case DIR_TEMP: + if (!GetTempDir(result)) + return false; + return true; + case base::DIR_HOME: + *result = GetHomeDir(); + return true; + case DIR_TEST_DATA: { + FilePath test_data_path; + if (!PathService::Get(DIR_SOURCE_ROOT, &test_data_path)) + return false; + test_data_path = test_data_path.Append(FILE_PATH_LITERAL("base")); + test_data_path = test_data_path.Append(FILE_PATH_LITERAL("test")); + test_data_path = test_data_path.Append(FILE_PATH_LITERAL("data")); + if (!PathExists(test_data_path)) // We don't want to create this. + return false; + *result = test_data_path; + return true; + } + default: + return false; + } +} + +} // namespace base diff --git a/base/base_paths.h b/base/base_paths.h new file mode 100644 index 0000000000000000000000000000000000000000..ef6aa82836205b7ac7fbc270abdeec2485cc2236 --- /dev/null +++ b/base/base_paths.h @@ -0,0 +1,54 @@ +// Copyright (c) 2012 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 BASE_BASE_PATHS_H_ +#define BASE_BASE_PATHS_H_ + +// This file declares path keys for the base module. These can be used with +// the PathService to access various special directories and files. + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include "base/base_paths_win.h" +#elif defined(OS_MACOSX) +#include "base/base_paths_mac.h" +#elif defined(OS_ANDROID) +#include "base/base_paths_android.h" +#endif + +#if defined(OS_POSIX) +#include "base/base_paths_posix.h" +#endif + +namespace base { + +enum BasePathKey { + PATH_START = 0, + + DIR_CURRENT, // Current directory. + DIR_EXE, // Directory containing FILE_EXE. + DIR_MODULE, // Directory containing FILE_MODULE. + DIR_TEMP, // Temporary directory. + DIR_HOME, // User's root home directory. On Windows this will look + // like "C:\Users\" which isn't necessarily a great + // place to put files. + FILE_EXE, // Path and filename of the current executable. + FILE_MODULE, // Path and filename of the module containing the code for + // the PathService (which could differ from FILE_EXE if the + // PathService were compiled into a shared object, for + // example). + DIR_SOURCE_ROOT, // Returns the root of the source tree. This key is useful + // for tests that need to locate various resources. It + // should not be used outside of test code. + DIR_USER_DESKTOP, // The current user's Desktop. + + DIR_TEST_DATA, // Used only for testing. + + PATH_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_H_ diff --git a/base/base_paths_posix.cc b/base/base_paths_posix.cc new file mode 100644 index 0000000000000000000000000000000000000000..37d646cd26111a2274bde8972afd0562cdfe1c03 --- /dev/null +++ b/base/base_paths_posix.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2012 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. + +// Defines base::PathProviderPosix, default path provider on POSIX OSes that +// don't have their own base_paths_OS.cc implementation (i.e. all but Mac and +// Android). + +#include "base/base_paths.h" + +#include +#include + +#include +#include +#include + +#include "base/environment.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +// Unused, and this file is not ported to libchrome. +// #include "base/nix/xdg_util.h" +#include "base/path_service.h" +#include "base/process/process_metrics.h" +#include "build/build_config.h" + +#if defined(OS_FREEBSD) +#include +#include +#elif defined(OS_SOLARIS) +#include +#endif + +namespace base { + +bool PathProviderPosix(int key, FilePath* result) { + FilePath path; + switch (key) { + case FILE_EXE: + case FILE_MODULE: { // TODO(evanm): is this correct? +#if defined(OS_LINUX) + FilePath bin_dir; + if (!ReadSymbolicLink(FilePath(kProcSelfExe), &bin_dir)) { + NOTREACHED() << "Unable to resolve " << kProcSelfExe << "."; + return false; + } + *result = bin_dir; + return true; +#elif defined(OS_FREEBSD) + int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + char bin_dir[PATH_MAX + 1]; + size_t length = sizeof(bin_dir); + // Upon return, |length| is the number of bytes written to |bin_dir| + // including the string terminator. + int error = sysctl(name, 4, bin_dir, &length, NULL, 0); + if (error < 0 || length <= 1) { + NOTREACHED() << "Unable to resolve path."; + return false; + } + *result = FilePath(FilePath::StringType(bin_dir, length - 1)); + return true; +#elif defined(OS_SOLARIS) + char bin_dir[PATH_MAX + 1]; + if (realpath(getexecname(), bin_dir) == NULL) { + NOTREACHED() << "Unable to resolve " << getexecname() << "."; + return false; + } + *result = FilePath(bin_dir); + return true; +#elif defined(OS_OPENBSD) + // There is currently no way to get the executable path on OpenBSD + char* cpath; + if ((cpath = getenv("CHROME_EXE_PATH")) != NULL) + *result = FilePath(cpath); + else + *result = FilePath("/usr/local/chrome/chrome"); + return true; +#endif + } +// Following paths are not supported in libchrome/libmojo. +#if 0 + case DIR_SOURCE_ROOT: { + // Allow passing this in the environment, for more flexibility in build + // tree configurations (sub-project builds, gyp --output_dir, etc.) + std::unique_ptr env(Environment::Create()); + std::string cr_source_root; + if (env->GetVar("CR_SOURCE_ROOT", &cr_source_root)) { + path = FilePath(cr_source_root); + if (PathExists(path)) { + *result = path; + return true; + } + DLOG(WARNING) << "CR_SOURCE_ROOT is set, but it appears to not " + << "point to a directory."; + } + // On POSIX, unit tests execute two levels deep from the source root. + // For example: out/{Debug|Release}/net_unittest + if (PathService::Get(DIR_EXE, &path)) { + *result = path.DirName().DirName(); + return true; + } + + DLOG(ERROR) << "Couldn't find your source root. " + << "Try running from your chromium/src directory."; + return false; + } + case DIR_USER_DESKTOP: + *result = nix::GetXDGUserDirectory("DESKTOP", "Desktop"); + return true; + case DIR_CACHE: { + std::unique_ptr env(Environment::Create()); + FilePath cache_dir( + nix::GetXDGDirectory(env.get(), "XDG_CACHE_HOME", ".cache")); + *result = cache_dir; + return true; + } +#endif + } + return false; +} + +} // namespace base diff --git a/base/base_paths_posix.h b/base/base_paths_posix.h new file mode 100644 index 0000000000000000000000000000000000000000..ef002aeb0bc3ed03593599869c7f0c571ee864e6 --- /dev/null +++ b/base/base_paths_posix.h @@ -0,0 +1,27 @@ +// Copyright (c) 2012 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 BASE_BASE_PATHS_POSIX_H_ +#define BASE_BASE_PATHS_POSIX_H_ + +// This file declares windows-specific path keys for the base module. +// These can be used with the PathService to access various special +// directories and files. + +namespace base { + +enum { + PATH_POSIX_START = 400, + + DIR_CACHE, // Directory where to put cache data. Note this is + // *not* where the browser cache lives, but the + // browser cache can be a subdirectory. + // This is $XDG_CACHE_HOME on Linux and + // ~/Library/Caches on Mac. + PATH_POSIX_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_POSIX_H_ diff --git a/base/debug/proc_maps_linux.cc b/base/debug/proc_maps_linux.cc new file mode 100644 index 0000000000000000000000000000000000000000..0bb44b45ac3ca48259c53c0b0e1944e86fc5f555 --- /dev/null +++ b/base/debug/proc_maps_linux.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2013 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. + +#include "base/debug/proc_maps_linux.h" + +#include +#include + +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/strings/string_split.h" +#include "build/build_config.h" + +#if defined(OS_LINUX) || defined(OS_ANDROID) +#include +#endif + +#if defined(OS_ANDROID) && !defined(__LP64__) +// In 32-bit mode, Bionic's inttypes.h defines PRI/SCNxPTR as an +// unsigned long int, which is incompatible with Bionic's stdint.h +// defining uintptr_t as an unsigned int: +// https://code.google.com/p/android/issues/detail?id=57218 +#undef SCNxPTR +#define SCNxPTR "x" +#endif + +namespace base { +namespace debug { + +// Scans |proc_maps| starting from |pos| returning true if the gate VMA was +// found, otherwise returns false. +static bool ContainsGateVMA(std::string* proc_maps, size_t pos) { +#if defined(ARCH_CPU_ARM_FAMILY) + // The gate VMA on ARM kernels is the interrupt vectors page. + return proc_maps->find(" [vectors]\n", pos) != std::string::npos; +#elif defined(ARCH_CPU_X86_64) + // The gate VMA on x86 64-bit kernels is the virtual system call page. + return proc_maps->find(" [vsyscall]\n", pos) != std::string::npos; +#else + // Otherwise assume there is no gate VMA in which case we shouldn't + // get duplicate entires. + return false; +#endif +} + +bool ReadProcMaps(std::string* proc_maps) { + // seq_file only writes out a page-sized amount on each call. Refer to header + // file for details. + const long kReadSize = sysconf(_SC_PAGESIZE); + + base::ScopedFD fd(HANDLE_EINTR(open("/proc/self/maps", O_RDONLY))); + if (!fd.is_valid()) { + DPLOG(ERROR) << "Couldn't open /proc/self/maps"; + return false; + } + proc_maps->clear(); + + while (true) { + // To avoid a copy, resize |proc_maps| so read() can write directly into it. + // Compute |buffer| afterwards since resize() may reallocate. + size_t pos = proc_maps->size(); + proc_maps->resize(pos + kReadSize); + void* buffer = &(*proc_maps)[pos]; + + ssize_t bytes_read = HANDLE_EINTR(read(fd.get(), buffer, kReadSize)); + if (bytes_read < 0) { + DPLOG(ERROR) << "Couldn't read /proc/self/maps"; + proc_maps->clear(); + return false; + } + + // ... and don't forget to trim off excess bytes. + proc_maps->resize(pos + bytes_read); + + if (bytes_read == 0) + break; + + // The gate VMA is handled as a special case after seq_file has finished + // iterating through all entries in the virtual memory table. + // + // Unfortunately, if additional entries are added at this point in time + // seq_file gets confused and the next call to read() will return duplicate + // entries including the gate VMA again. + // + // Avoid this by searching for the gate VMA and breaking early. + if (ContainsGateVMA(proc_maps, pos)) + break; + } + + return true; +} + +bool ParseProcMaps(const std::string& input, + std::vector* regions_out) { + CHECK(regions_out); + std::vector regions; + + // This isn't async safe nor terribly efficient, but it doesn't need to be at + // this point in time. + std::vector lines = SplitString( + input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + for (size_t i = 0; i < lines.size(); ++i) { + // Due to splitting on '\n' the last line should be empty. + if (i == lines.size() - 1) { + if (!lines[i].empty()) { + DLOG(WARNING) << "Last line not empty"; + return false; + } + break; + } + + MappedMemoryRegion region; + const char* line = lines[i].c_str(); + char permissions[5] = {'\0'}; // Ensure NUL-terminated string. + uint8_t dev_major = 0; + uint8_t dev_minor = 0; + long inode = 0; + int path_index = 0; + + // Sample format from man 5 proc: + // + // address perms offset dev inode pathname + // 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm + // + // The final %n term captures the offset in the input string, which is used + // to determine the path name. It *does not* increment the return value. + // Refer to man 3 sscanf for details. + if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n", + ®ion.start, ®ion.end, permissions, ®ion.offset, + &dev_major, &dev_minor, &inode, &path_index) < 7) { + DPLOG(WARNING) << "sscanf failed for line: " << line; + return false; + } + + region.permissions = 0; + + if (permissions[0] == 'r') + region.permissions |= MappedMemoryRegion::READ; + else if (permissions[0] != '-') + return false; + + if (permissions[1] == 'w') + region.permissions |= MappedMemoryRegion::WRITE; + else if (permissions[1] != '-') + return false; + + if (permissions[2] == 'x') + region.permissions |= MappedMemoryRegion::EXECUTE; + else if (permissions[2] != '-') + return false; + + if (permissions[3] == 'p') + region.permissions |= MappedMemoryRegion::PRIVATE; + else if (permissions[3] != 's' && permissions[3] != 'S') // Shared memory. + return false; + + // Pushing then assigning saves us a string copy. + regions.push_back(region); + regions.back().path.assign(line + path_index); + } + + regions_out->swap(regions); + return true; +} + +} // namespace debug +} // namespace base diff --git a/base/debug/stack_trace_android.cc b/base/debug/stack_trace_android.cc new file mode 100644 index 0000000000000000000000000000000000000000..329204c8c6a3824fbac6b102a25ab7152ef3addb --- /dev/null +++ b/base/debug/stack_trace_android.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2012 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. + +#include "base/debug/stack_trace.h" + +#include +#include +#include + +#include +#include + +#include "base/debug/proc_maps_linux.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_restrictions.h" + +#ifdef __LP64__ +#define FMT_ADDR "0x%016lx" +#else +#define FMT_ADDR "0x%08x" +#endif + +namespace { + +struct StackCrawlState { + StackCrawlState(uintptr_t* frames, size_t max_depth) + : frames(frames), + frame_count(0), + max_depth(max_depth), + have_skipped_self(false) {} + + uintptr_t* frames; + size_t frame_count; + size_t max_depth; + bool have_skipped_self; +}; + +_Unwind_Reason_Code TraceStackFrame(_Unwind_Context* context, void* arg) { + StackCrawlState* state = static_cast(arg); + uintptr_t ip = _Unwind_GetIP(context); + + // The first stack frame is this function itself. Skip it. + if (ip != 0 && !state->have_skipped_self) { + state->have_skipped_self = true; + return _URC_NO_REASON; + } + + state->frames[state->frame_count++] = ip; + if (state->frame_count >= state->max_depth) + return _URC_END_OF_STACK; + return _URC_NO_REASON; +} + +} // namespace + +namespace base { +namespace debug { + +bool EnableInProcessStackDumping() { + // When running in an application, our code typically expects SIGPIPE + // to be ignored. Therefore, when testing that same code, it should run + // with SIGPIPE ignored as well. + // TODO(phajdan.jr): De-duplicate this SIGPIPE code. + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = SIG_IGN; + sigemptyset(&action.sa_mask); + return (sigaction(SIGPIPE, &action, NULL) == 0); +} + +StackTrace::StackTrace(size_t count) { + count = std::min(arraysize(trace_), count); + + StackCrawlState state(reinterpret_cast(trace_), count); + _Unwind_Backtrace(&TraceStackFrame, &state); + count_ = state.frame_count; +} + +void StackTrace::Print() const { + std::string backtrace = ToString(); + __android_log_write(ANDROID_LOG_ERROR, "chromium", backtrace.c_str()); +} + +// NOTE: Native libraries in APKs are stripped before installing. Print out the +// relocatable address and library names so host computers can use tools to +// symbolize and demangle (e.g., addr2line, c++filt). +void StackTrace::OutputToStream(std::ostream* os) const { + std::string proc_maps; + std::vector regions; + // Allow IO to read /proc/self/maps. Reading this file doesn't hit the disk + // since it lives in procfs, and this is currently used to print a stack trace + // on fatal log messages in debug builds only. If the restriction is enabled + // then it will recursively trigger fatal failures when this enters on the + // UI thread. + base::ThreadRestrictions::ScopedAllowIO allow_io; + if (!ReadProcMaps(&proc_maps)) { + __android_log_write( + ANDROID_LOG_ERROR, "chromium", "Failed to read /proc/self/maps"); + } else if (!ParseProcMaps(proc_maps, ®ions)) { + __android_log_write( + ANDROID_LOG_ERROR, "chromium", "Failed to parse /proc/self/maps"); + } + + for (size_t i = 0; i < count_; ++i) { + // Subtract one as return address of function may be in the next + // function when a function is annotated as noreturn. + uintptr_t address = reinterpret_cast(trace_[i]) - 1; + + std::vector::iterator iter = regions.begin(); + while (iter != regions.end()) { + if (address >= iter->start && address < iter->end && + !iter->path.empty()) { + break; + } + ++iter; + } + + *os << base::StringPrintf("#%02zd " FMT_ADDR " ", i, address); + + if (iter != regions.end()) { + uintptr_t rel_pc = address - iter->start + iter->offset; + const char* path = iter->path.c_str(); + *os << base::StringPrintf("%s+" FMT_ADDR, path, rel_pc); + } else { + *os << ""; + } + + *os << "\n"; + } +} + +} // namespace debug +} // namespace base diff --git a/base/debug/stack_trace_posix.cc b/base/debug/stack_trace_posix.cc index ab4c34b021cc1a6185c54fea335c0218d510c0f8..4a55f64cc6c1a16069c18451ab85c9d77f207d2f 100644 --- a/base/debug/stack_trace_posix.cc +++ b/base/debug/stack_trace_posix.cc @@ -59,7 +59,7 @@ namespace { volatile sig_atomic_t in_signal_handler = 0; -#if !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) +#if !defined(USE_SYMBOLIZE) // The prefix used for mangled symbols, per the Itanium C++ ABI: // http://www.codesourcery.com/cxx-abi/abi.html#mangling const char kMangledSymbolPrefix[] = "_Z"; @@ -68,9 +68,9 @@ const char kMangledSymbolPrefix[] = "_Z"; // (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join const char kSymbolCharacters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; -#endif // !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) +#endif // !defined(USE_SYMBOLIZE) -#if !defined(USE_SYMBOLIZE) && !defined(__UCLIBC__) +#if !defined(USE_SYMBOLIZE) // Demangles C++ symbols in the given text. Example: // // "out/Debug/base_unittests(_ZN10StackTraceC1Ev+0x20) [0x817778c]" @@ -79,7 +79,8 @@ const char kSymbolCharacters[] = void DemangleSymbols(std::string* text) { // Note: code in this function is NOT async-signal safe (std::string uses // malloc internally). -#if defined(__GLIBCXX__) && !defined(__UCLIBC__) + +#if !defined(__UCLIBC__) std::string::size_type search_from = 0; while (search_from < text->size()) { @@ -115,7 +116,7 @@ void DemangleSymbols(std::string* text) { search_from = mangled_start + 2; } } -#endif // defined(__GLIBCXX__) && !defined(__UCLIBC__) +#endif // !defined(__UCLIBC__) } #endif // !defined(USE_SYMBOLIZE) diff --git a/base/event_types.h b/base/event_types.h deleted file mode 100644 index 9905800d2e8c2f1096af350a39394a24703091a2..0000000000000000000000000000000000000000 --- a/base/event_types.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2012 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 BASE_EVENT_TYPES_H_ -#define BASE_EVENT_TYPES_H_ - -#include "build/build_config.h" - -#if defined(OS_WIN) -#include -#elif defined(USE_X11) -typedef union _XEvent XEvent; -#elif defined(OS_MACOSX) -#if defined(__OBJC__) -@class NSEvent; -#else // __OBJC__ -class NSEvent; -#endif // __OBJC__ -#endif - -namespace base { - -// Cross platform typedefs for native event types. -#if defined(OS_WIN) -typedef MSG NativeEvent; -#elif defined(USE_X11) -typedef XEvent* NativeEvent; -#elif defined(OS_MACOSX) -typedef NSEvent* NativeEvent; -#else -typedef void* NativeEvent; -#endif - -} // namespace base - -#endif // BASE_EVENT_TYPES_H_ diff --git a/base/files/file_util_posix.cc b/base/files/file_util_posix.cc index 3501e241e94c52fd69220dd35a3d2feea78d8625..91f12030a09dd255f2f98b5ece618be3e1789ddb 100644 --- a/base/files/file_util_posix.cc +++ b/base/files/file_util_posix.cc @@ -29,6 +29,7 @@ #include "base/logging.h" #include "base/macros.h" #include "base/memory/singleton.h" +#include "base/path_service.h" #include "base/posix/eintr_wrapper.h" #include "base/stl_util.h" #include "base/strings/string_split.h" @@ -49,7 +50,6 @@ #if defined(OS_ANDROID) #include "base/android/content_uri_utils.h" #include "base/os_compat_android.h" -#include "base/path_service.h" #endif #if !defined(OS_IOS) diff --git a/base/i18n/base_i18n_export.h b/base/i18n/base_i18n_export.h new file mode 100644 index 0000000000000000000000000000000000000000..e8a2adda17d41cee5cfe26c4c7ebdf41af2b0504 --- /dev/null +++ b/base/i18n/base_i18n_export.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 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 BASE_I18N_BASE_I18N_EXPORT_H_ +#define BASE_I18N_BASE_I18N_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(BASE_I18N_IMPLEMENTATION) +#define BASE_I18N_EXPORT __declspec(dllexport) +#else +#define BASE_I18N_EXPORT __declspec(dllimport) +#endif // defined(BASE_I18N_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(BASE_I18N_IMPLEMENTATION) +#define BASE_I18N_EXPORT __attribute__((visibility("default"))) +#else +#define BASE_I18N_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define BASE_I18N_EXPORT +#endif + +#endif // BASE_I18N_BASE_I18N_EXPORT_H_ diff --git a/base/i18n/rtl.h b/base/i18n/rtl.h new file mode 100644 index 0000000000000000000000000000000000000000..df15cd0208a86738d84563db4c9b9f20aafc256f --- /dev/null +++ b/base/i18n/rtl.h @@ -0,0 +1,155 @@ +// Copyright (c) 2011 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 BASE_I18N_RTL_H_ +#define BASE_I18N_RTL_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/i18n/base_i18n_export.h" +#include "base/strings/string16.h" +#include "build/build_config.h" + +namespace base { + +class FilePath; + +namespace i18n { + +const char16 kRightToLeftMark = 0x200F; +const char16 kLeftToRightMark = 0x200E; +const char16 kLeftToRightEmbeddingMark = 0x202A; +const char16 kRightToLeftEmbeddingMark = 0x202B; +const char16 kPopDirectionalFormatting = 0x202C; +const char16 kLeftToRightOverride = 0x202D; +const char16 kRightToLeftOverride = 0x202E; + +// Locale.java mirrored this enum TextDirection. Please keep in sync. +enum TextDirection { + UNKNOWN_DIRECTION = 0, + RIGHT_TO_LEFT = 1, + LEFT_TO_RIGHT = 2, + TEXT_DIRECTION_MAX = LEFT_TO_RIGHT, +}; + +// Get the locale that the currently running process has been configured to use. +// The return value is of the form language[-country] (e.g., en-US) where the +// language is the 2 or 3 letter code from ISO-639. +BASE_I18N_EXPORT std::string GetConfiguredLocale(); + +// Canonicalize a string (eg. a POSIX locale string) to a Chrome locale name. +BASE_I18N_EXPORT std::string GetCanonicalLocale(const std::string& locale); + +// Sets the default locale of ICU. +// Once the application locale of Chrome in GetApplicationLocale is determined, +// the default locale of ICU need to be changed to match the application locale +// so that ICU functions work correctly in a locale-dependent manner. +// This is handy in that we don't have to call GetApplicationLocale() +// everytime we call locale-dependent ICU APIs as long as we make sure +// that this is called before any locale-dependent API is called. +BASE_I18N_EXPORT void SetICUDefaultLocale(const std::string& locale_string); + +// Returns true if the application text direction is right-to-left. +BASE_I18N_EXPORT bool IsRTL(); + +// Returns whether the text direction for the default ICU locale is RTL. This +// assumes that SetICUDefaultLocale has been called to set the default locale to +// the UI locale of Chrome. +// NOTE: Generally, you should call IsRTL() instead of this. +BASE_I18N_EXPORT bool ICUIsRTL(); + +// Returns the text direction for |locale_name|. +// As a startup optimization, this method checks the locale against a list of +// Chrome-supported RTL locales. +BASE_I18N_EXPORT TextDirection +GetTextDirectionForLocaleInStartUp(const char* locale_name); + +// Returns the text direction for |locale_name|. +BASE_I18N_EXPORT TextDirection GetTextDirectionForLocale( + const char* locale_name); + +// Given the string in |text|, returns the directionality of the first or last +// character with strong directionality in the string. If no character in the +// text has strong directionality, LEFT_TO_RIGHT is returned. The Bidi +// character types L, LRE, LRO, R, AL, RLE, and RLO are considered as strong +// directionality characters. Please refer to http://unicode.org/reports/tr9/ +// for more information. +BASE_I18N_EXPORT TextDirection GetFirstStrongCharacterDirection( + const string16& text); +BASE_I18N_EXPORT TextDirection GetLastStrongCharacterDirection( + const string16& text); + +// Given the string in |text|, returns LEFT_TO_RIGHT or RIGHT_TO_LEFT if all the +// strong directionality characters in the string are of the same +// directionality. It returns UNKNOWN_DIRECTION if the string contains a mix of +// LTR and RTL strong directionality characters. Defaults to LEFT_TO_RIGHT if +// the string does not contain directionality characters. Please refer to +// http://unicode.org/reports/tr9/ for more information. +BASE_I18N_EXPORT TextDirection GetStringDirection(const string16& text); + +// Given the string in |text|, this function modifies the string in place with +// the appropriate Unicode formatting marks that mark the string direction +// (either left-to-right or right-to-left). The function checks both the current +// locale and the contents of the string in order to determine the direction of +// the returned string. The function returns true if the string in |text| was +// properly adjusted. +// +// Certain LTR strings are not rendered correctly when the context is RTL. For +// example, the string "Foo!" will appear as "!Foo" if it is rendered as is in +// an RTL context. Calling this function will make sure the returned localized +// string is always treated as a right-to-left string. This is done by +// inserting certain Unicode formatting marks into the returned string. +// +// ** Notes about the Windows version of this function: +// TODO(idana) bug 6806: this function adjusts the string in question only +// if the current locale is right-to-left. The function does not take care of +// the opposite case (an RTL string displayed in an LTR context) since +// adjusting the string involves inserting Unicode formatting characters that +// Windows does not handle well unless right-to-left language support is +// installed. Since the English version of Windows doesn't have right-to-left +// language support installed by default, inserting the direction Unicode mark +// results in Windows displaying squares. +BASE_I18N_EXPORT bool AdjustStringForLocaleDirection(string16* text); + +// Undoes the actions of the above function (AdjustStringForLocaleDirection). +BASE_I18N_EXPORT bool UnadjustStringForLocaleDirection(string16* text); + +// Returns true if the string contains at least one character with strong right +// to left directionality; that is, a character with either R or AL Unicode +// BiDi character type. +BASE_I18N_EXPORT bool StringContainsStrongRTLChars(const string16& text); + +// Wraps a string with an LRE-PDF pair which essentialy marks the string as a +// Left-To-Right string. Doing this is useful in order to make sure LTR +// strings are rendered properly in an RTL context. +BASE_I18N_EXPORT void WrapStringWithLTRFormatting(string16* text); + +// Wraps a string with an RLE-PDF pair which essentialy marks the string as a +// Right-To-Left string. Doing this is useful in order to make sure RTL +// strings are rendered properly in an LTR context. +BASE_I18N_EXPORT void WrapStringWithRTLFormatting(string16* text); + +// Wraps file path to get it to display correctly in RTL UI. All filepaths +// should be passed through this function before display in UI for RTL locales. +BASE_I18N_EXPORT void WrapPathWithLTRFormatting(const FilePath& path, + string16* rtl_safe_path); + +// Return the string in |text| wrapped with LRE (Left-To-Right Embedding) and +// PDF (Pop Directional Formatting) marks, if needed for UI display purposes. +BASE_I18N_EXPORT string16 GetDisplayStringInLTRDirectionality( + const string16& text) WARN_UNUSED_RESULT; + +// Strip the beginning (U+202A..U+202B, U+202D..U+202E) and/or ending (U+202C) +// explicit bidi control characters from |text|, if there are any. Otherwise, +// return the text itself. Explicit bidi control characters display and have +// semantic effect. They can be deleted so they might not always appear in a +// pair. +BASE_I18N_EXPORT string16 StripWrappingBidiControlCharacters( + const string16& text) WARN_UNUSED_RESULT; + +} // namespace i18n +} // namespace base + +#endif // BASE_I18N_RTL_H_ diff --git a/base/id_map.h b/base/id_map.h deleted file mode 100644 index d171fb14c102cefc5b5ec332315e8a7303f86b9c..0000000000000000000000000000000000000000 --- a/base/id_map.h +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) 2011 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 BASE_ID_MAP_H_ -#define BASE_ID_MAP_H_ - -#include -#include -#include -#include -#include -#include - -#include "base/containers/hash_tables.h" -#include "base/logging.h" -#include "base/macros.h" -#include "base/sequence_checker.h" - -// This object maintains a list of IDs that can be quickly converted to -// pointers to objects. It is implemented as a hash table, optimized for -// relatively small data sets (in the common case, there will be exactly one -// item in the list). -// -// Items can be inserted into the container with arbitrary ID, but the caller -// must ensure they are unique. Inserting IDs and relying on automatically -// generated ones is not allowed because they can collide. - -// The map's value type (the V param) can be any dereferenceable type, such as a -// raw pointer or smart pointer -template -class IDMap final { - public: - using KeyType = K; - - private: - using T = typename std::remove_reference::type; - using HashTable = base::hash_map; - - public: - IDMap() : iteration_depth_(0), next_id_(1), check_on_null_data_(false) { - // A number of consumers of IDMap create it on one thread but always - // access it from a different, but consistent, thread (or sequence) - // post-construction. The first call to CalledOnValidSequence() will re-bind - // it. - sequence_checker_.DetachFromSequence(); - } - - ~IDMap() { - // Many IDMap's are static, and hence will be destroyed on the main - // thread. However, all the accesses may take place on another thread (or - // sequence), such as the IO thread. Detaching again to clean this up. - sequence_checker_.DetachFromSequence(); - } - - // Sets whether Add and Replace should DCHECK if passed in NULL data. - // Default is false. - void set_check_on_null_data(bool value) { check_on_null_data_ = value; } - - // Adds a view with an automatically generated unique ID. See AddWithID. - KeyType Add(V data) { return AddInternal(std::move(data)); } - - // Adds a new data member with the specified ID. The ID must not be in - // the list. The caller either must generate all unique IDs itself and use - // this function, or allow this object to generate IDs and call Add. These - // two methods may not be mixed, or duplicate IDs may be generated. - void AddWithID(V data, KeyType id) { AddWithIDInternal(std::move(data), id); } - - void Remove(KeyType id) { - DCHECK(sequence_checker_.CalledOnValidSequence()); - typename HashTable::iterator i = data_.find(id); - if (i == data_.end()) { - NOTREACHED() << "Attempting to remove an item not in the list"; - return; - } - - if (iteration_depth_ == 0) { - data_.erase(i); - } else { - removed_ids_.insert(id); - } - } - - // Replaces the value for |id| with |new_data| and returns the existing value. - // Should only be called with an already added id. - V Replace(KeyType id, V new_data) { - DCHECK(sequence_checker_.CalledOnValidSequence()); - DCHECK(!check_on_null_data_ || new_data); - typename HashTable::iterator i = data_.find(id); - DCHECK(i != data_.end()); - - std::swap(i->second, new_data); - return new_data; - } - - void Clear() { - DCHECK(sequence_checker_.CalledOnValidSequence()); - if (iteration_depth_ == 0) { - data_.clear(); - } else { - for (typename HashTable::iterator i = data_.begin(); - i != data_.end(); ++i) - removed_ids_.insert(i->first); - } - } - - bool IsEmpty() const { - DCHECK(sequence_checker_.CalledOnValidSequence()); - return size() == 0u; - } - - T* Lookup(KeyType id) const { - DCHECK(sequence_checker_.CalledOnValidSequence()); - typename HashTable::const_iterator i = data_.find(id); - if (i == data_.end()) - return nullptr; - return &*i->second; - } - - size_t size() const { - DCHECK(sequence_checker_.CalledOnValidSequence()); - return data_.size() - removed_ids_.size(); - } - -#if defined(UNIT_TEST) - int iteration_depth() const { - return iteration_depth_; - } -#endif // defined(UNIT_TEST) - - // It is safe to remove elements from the map during iteration. All iterators - // will remain valid. - template - class Iterator { - public: - Iterator(IDMap* map) : map_(map), iter_(map_->data_.begin()) { - Init(); - } - - Iterator(const Iterator& iter) - : map_(iter.map_), - iter_(iter.iter_) { - Init(); - } - - const Iterator& operator=(const Iterator& iter) { - map_ = iter.map; - iter_ = iter.iter; - Init(); - return *this; - } - - ~Iterator() { - DCHECK(map_->sequence_checker_.CalledOnValidSequence()); - - // We're going to decrement iteration depth. Make sure it's greater than - // zero so that it doesn't become negative. - DCHECK_LT(0, map_->iteration_depth_); - - if (--map_->iteration_depth_ == 0) - map_->Compact(); - } - - bool IsAtEnd() const { - DCHECK(map_->sequence_checker_.CalledOnValidSequence()); - return iter_ == map_->data_.end(); - } - - KeyType GetCurrentKey() const { - DCHECK(map_->sequence_checker_.CalledOnValidSequence()); - return iter_->first; - } - - ReturnType* GetCurrentValue() const { - DCHECK(map_->sequence_checker_.CalledOnValidSequence()); - return &*iter_->second; - } - - void Advance() { - DCHECK(map_->sequence_checker_.CalledOnValidSequence()); - ++iter_; - SkipRemovedEntries(); - } - - private: - void Init() { - DCHECK(map_->sequence_checker_.CalledOnValidSequence()); - ++map_->iteration_depth_; - SkipRemovedEntries(); - } - - void SkipRemovedEntries() { - while (iter_ != map_->data_.end() && - map_->removed_ids_.find(iter_->first) != - map_->removed_ids_.end()) { - ++iter_; - } - } - - IDMap* map_; - typename HashTable::const_iterator iter_; - }; - - typedef Iterator iterator; - typedef Iterator const_iterator; - - private: - KeyType AddInternal(V data) { - DCHECK(sequence_checker_.CalledOnValidSequence()); - DCHECK(!check_on_null_data_ || data); - KeyType this_id = next_id_; - DCHECK(data_.find(this_id) == data_.end()) << "Inserting duplicate item"; - data_[this_id] = std::move(data); - next_id_++; - return this_id; - } - - void AddWithIDInternal(V data, KeyType id) { - DCHECK(sequence_checker_.CalledOnValidSequence()); - DCHECK(!check_on_null_data_ || data); - DCHECK(data_.find(id) == data_.end()) << "Inserting duplicate item"; - data_[id] = std::move(data); - } - - void Compact() { - DCHECK_EQ(0, iteration_depth_); - for (const auto& i : removed_ids_) - Remove(i); - removed_ids_.clear(); - } - - // Keep track of how many iterators are currently iterating on us to safely - // handle removing items during iteration. - int iteration_depth_; - - // Keep set of IDs that should be removed after the outermost iteration has - // finished. This way we manage to not invalidate the iterator when an element - // is removed. - std::set removed_ids_; - - // The next ID that we will return from Add() - KeyType next_id_; - - HashTable data_; - - // See description above setter. - bool check_on_null_data_; - - base::SequenceChecker sequence_checker_; - - DISALLOW_COPY_AND_ASSIGN(IDMap); -}; - -#endif // BASE_ID_MAP_H_ diff --git a/base/id_map_unittest.cc b/base/id_map_unittest.cc deleted file mode 100644 index 42949bb5b95c25ba3e91e5fd801693e0fe359ba6..0000000000000000000000000000000000000000 --- a/base/id_map_unittest.cc +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright (c) 2011 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. - -#include "base/id_map.h" - -#include - -#include - -#include "base/memory/ptr_util.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace { - -class TestObject { -}; - -class DestructorCounter { - public: - explicit DestructorCounter(int* counter) : counter_(counter) {} - ~DestructorCounter() { ++(*counter_); } - - private: - int* counter_; -}; - -TEST(IDMapTest, Basic) { - IDMap map; - EXPECT_TRUE(map.IsEmpty()); - EXPECT_EQ(0U, map.size()); - - TestObject obj1; - TestObject obj2; - - int32_t id1 = map.Add(&obj1); - EXPECT_FALSE(map.IsEmpty()); - EXPECT_EQ(1U, map.size()); - EXPECT_EQ(&obj1, map.Lookup(id1)); - - int32_t id2 = map.Add(&obj2); - EXPECT_FALSE(map.IsEmpty()); - EXPECT_EQ(2U, map.size()); - - EXPECT_EQ(&obj1, map.Lookup(id1)); - EXPECT_EQ(&obj2, map.Lookup(id2)); - - map.Remove(id1); - EXPECT_FALSE(map.IsEmpty()); - EXPECT_EQ(1U, map.size()); - - map.Remove(id2); - EXPECT_TRUE(map.IsEmpty()); - EXPECT_EQ(0U, map.size()); - - map.AddWithID(&obj1, 1); - map.AddWithID(&obj2, 2); - EXPECT_EQ(&obj1, map.Lookup(1)); - EXPECT_EQ(&obj2, map.Lookup(2)); - - EXPECT_EQ(&obj2, map.Replace(2, &obj1)); - EXPECT_EQ(&obj1, map.Lookup(2)); - - EXPECT_EQ(0, map.iteration_depth()); -} - -TEST(IDMapTest, IteratorRemainsValidWhenRemovingCurrentElement) { - IDMap map; - - TestObject obj1; - TestObject obj2; - TestObject obj3; - - map.Add(&obj1); - map.Add(&obj2); - map.Add(&obj3); - - { - IDMap::const_iterator iter(&map); - - EXPECT_EQ(1, map.iteration_depth()); - - while (!iter.IsAtEnd()) { - map.Remove(iter.GetCurrentKey()); - iter.Advance(); - } - - // Test that while an iterator is still in scope, we get the map emptiness - // right (http://crbug.com/35571). - EXPECT_TRUE(map.IsEmpty()); - EXPECT_EQ(0U, map.size()); - } - - EXPECT_TRUE(map.IsEmpty()); - EXPECT_EQ(0U, map.size()); - - EXPECT_EQ(0, map.iteration_depth()); -} - -TEST(IDMapTest, IteratorRemainsValidWhenRemovingOtherElements) { - IDMap map; - - const int kCount = 5; - TestObject obj[kCount]; - - for (int i = 0; i < kCount; i++) - map.Add(&obj[i]); - - // IDMap uses a hash_map, which has no predictable iteration order. - int32_t ids_in_iteration_order[kCount]; - const TestObject* objs_in_iteration_order[kCount]; - int counter = 0; - for (IDMap::const_iterator iter(&map); !iter.IsAtEnd(); - iter.Advance()) { - ids_in_iteration_order[counter] = iter.GetCurrentKey(); - objs_in_iteration_order[counter] = iter.GetCurrentValue(); - counter++; - } - - counter = 0; - for (IDMap::const_iterator iter(&map); !iter.IsAtEnd(); - iter.Advance()) { - EXPECT_EQ(1, map.iteration_depth()); - - switch (counter) { - case 0: - EXPECT_EQ(ids_in_iteration_order[0], iter.GetCurrentKey()); - EXPECT_EQ(objs_in_iteration_order[0], iter.GetCurrentValue()); - map.Remove(ids_in_iteration_order[1]); - break; - case 1: - EXPECT_EQ(ids_in_iteration_order[2], iter.GetCurrentKey()); - EXPECT_EQ(objs_in_iteration_order[2], iter.GetCurrentValue()); - map.Remove(ids_in_iteration_order[3]); - break; - case 2: - EXPECT_EQ(ids_in_iteration_order[4], iter.GetCurrentKey()); - EXPECT_EQ(objs_in_iteration_order[4], iter.GetCurrentValue()); - map.Remove(ids_in_iteration_order[0]); - break; - default: - FAIL() << "should not have that many elements"; - break; - } - - counter++; - } - - EXPECT_EQ(0, map.iteration_depth()); -} - -TEST(IDMapTest, CopyIterator) { - IDMap map; - - TestObject obj1; - TestObject obj2; - TestObject obj3; - - map.Add(&obj1); - map.Add(&obj2); - map.Add(&obj3); - - EXPECT_EQ(0, map.iteration_depth()); - - { - IDMap::const_iterator iter1(&map); - EXPECT_EQ(1, map.iteration_depth()); - - // Make sure that copying the iterator correctly increments - // map's iteration depth. - IDMap::const_iterator iter2(iter1); - EXPECT_EQ(2, map.iteration_depth()); - } - - // Make sure after destroying all iterators the map's iteration depth - // returns to initial state. - EXPECT_EQ(0, map.iteration_depth()); -} - -TEST(IDMapTest, AssignIterator) { - IDMap map; - - TestObject obj1; - TestObject obj2; - TestObject obj3; - - map.Add(&obj1); - map.Add(&obj2); - map.Add(&obj3); - - EXPECT_EQ(0, map.iteration_depth()); - - { - IDMap::const_iterator iter1(&map); - EXPECT_EQ(1, map.iteration_depth()); - - IDMap::const_iterator iter2(&map); - EXPECT_EQ(2, map.iteration_depth()); - - // Make sure that assigning the iterator correctly updates - // map's iteration depth (-1 for destruction, +1 for assignment). - EXPECT_EQ(2, map.iteration_depth()); - } - - // Make sure after destroying all iterators the map's iteration depth - // returns to initial state. - EXPECT_EQ(0, map.iteration_depth()); -} - -TEST(IDMapTest, IteratorRemainsValidWhenClearing) { - IDMap map; - - const int kCount = 5; - TestObject obj[kCount]; - - for (int i = 0; i < kCount; i++) - map.Add(&obj[i]); - - // IDMap uses a hash_map, which has no predictable iteration order. - int32_t ids_in_iteration_order[kCount]; - const TestObject* objs_in_iteration_order[kCount]; - int counter = 0; - for (IDMap::const_iterator iter(&map); !iter.IsAtEnd(); - iter.Advance()) { - ids_in_iteration_order[counter] = iter.GetCurrentKey(); - objs_in_iteration_order[counter] = iter.GetCurrentValue(); - counter++; - } - - counter = 0; - for (IDMap::const_iterator iter(&map); !iter.IsAtEnd(); - iter.Advance()) { - switch (counter) { - case 0: - EXPECT_EQ(ids_in_iteration_order[0], iter.GetCurrentKey()); - EXPECT_EQ(objs_in_iteration_order[0], iter.GetCurrentValue()); - break; - case 1: - EXPECT_EQ(ids_in_iteration_order[1], iter.GetCurrentKey()); - EXPECT_EQ(objs_in_iteration_order[1], iter.GetCurrentValue()); - map.Clear(); - EXPECT_TRUE(map.IsEmpty()); - EXPECT_EQ(0U, map.size()); - break; - default: - FAIL() << "should not have that many elements"; - break; - } - counter++; - } - - EXPECT_TRUE(map.IsEmpty()); - EXPECT_EQ(0U, map.size()); -} - -TEST(IDMapTest, OwningPointersDeletesThemOnRemove) { - const int kCount = 3; - - int external_del_count = 0; - DestructorCounter* external_obj[kCount]; - int map_external_ids[kCount]; - - int owned_del_count = 0; - int map_owned_ids[kCount]; - - IDMap map_external; - IDMap> map_owned; - - for (int i = 0; i < kCount; ++i) { - external_obj[i] = new DestructorCounter(&external_del_count); - map_external_ids[i] = map_external.Add(external_obj[i]); - - map_owned_ids[i] = - map_owned.Add(base::MakeUnique(&owned_del_count)); - } - - for (int i = 0; i < kCount; ++i) { - EXPECT_EQ(external_del_count, 0); - EXPECT_EQ(owned_del_count, i); - - map_external.Remove(map_external_ids[i]); - map_owned.Remove(map_owned_ids[i]); - } - - for (int i = 0; i < kCount; ++i) { - delete external_obj[i]; - } - - EXPECT_EQ(external_del_count, kCount); - EXPECT_EQ(owned_del_count, kCount); -} - -TEST(IDMapTest, OwningPointersDeletesThemOnClear) { - const int kCount = 3; - - int external_del_count = 0; - DestructorCounter* external_obj[kCount]; - - int owned_del_count = 0; - - IDMap map_external; - IDMap> map_owned; - - for (int i = 0; i < kCount; ++i) { - external_obj[i] = new DestructorCounter(&external_del_count); - map_external.Add(external_obj[i]); - - map_owned.Add(base::MakeUnique(&owned_del_count)); - } - - EXPECT_EQ(external_del_count, 0); - EXPECT_EQ(owned_del_count, 0); - - map_external.Clear(); - map_owned.Clear(); - - EXPECT_EQ(external_del_count, 0); - EXPECT_EQ(owned_del_count, kCount); - - for (int i = 0; i < kCount; ++i) { - delete external_obj[i]; - } - - EXPECT_EQ(external_del_count, kCount); - EXPECT_EQ(owned_del_count, kCount); -} - -TEST(IDMapTest, OwningPointersDeletesThemOnDestruct) { - const int kCount = 3; - - int external_del_count = 0; - DestructorCounter* external_obj[kCount]; - - int owned_del_count = 0; - - { - IDMap map_external; - IDMap> map_owned; - - for (int i = 0; i < kCount; ++i) { - external_obj[i] = new DestructorCounter(&external_del_count); - map_external.Add(external_obj[i]); - - map_owned.Add(base::MakeUnique(&owned_del_count)); - } - } - - EXPECT_EQ(external_del_count, 0); - - for (int i = 0; i < kCount; ++i) { - delete external_obj[i]; - } - - EXPECT_EQ(external_del_count, kCount); - EXPECT_EQ(owned_del_count, kCount); -} - -TEST(IDMapTest, Int64KeyType) { - IDMap map; - TestObject obj1; - const int64_t kId1 = 999999999999999999; - - map.AddWithID(&obj1, kId1); - EXPECT_EQ(&obj1, map.Lookup(kId1)); - - IDMap::const_iterator iter(&map); - ASSERT_FALSE(iter.IsAtEnd()); - EXPECT_EQ(kId1, iter.GetCurrentKey()); - EXPECT_EQ(&obj1, iter.GetCurrentValue()); - iter.Advance(); - ASSERT_TRUE(iter.IsAtEnd()); - - map.Remove(kId1); - EXPECT_TRUE(map.IsEmpty()); -} - -} // namespace diff --git a/base/json/json_reader_unittest.cc b/base/json/json_reader_unittest.cc index 1344de639165c1cd8143479a523abc36878ba73d..f645b427fc269995ff9bc124d751a0aee977d537 100644 --- a/base/json/json_reader_unittest.cc +++ b/base/json/json_reader_unittest.cc @@ -8,14 +8,11 @@ #include -#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) #include "base/base_paths.h" -#include "base/path_service.h" -#endif - #include "base/files/file_util.h" #include "base/logging.h" #include "base/macros.h" +#include "base/path_service.h" #include "base/strings/string_piece.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" @@ -570,8 +567,7 @@ TEST(JSONReaderTest, Reading) { } } -#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) -TEST(JSONReaderTest, ReadFromFile) { +TEST(JSONReaderTest, DISABLED_ReadFromFile) { FilePath path; ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &path)); path = path.AppendASCII("json"); @@ -585,7 +581,6 @@ TEST(JSONReaderTest, ReadFromFile) { ASSERT_TRUE(root) << reader.GetErrorMessage(); EXPECT_TRUE(root->IsType(Value::Type::DICTIONARY)); } -#endif // !__ANDROID__ && !__ANDROID_HOST__ // Tests that the root of a JSON object can be deleted safely while its // children outlive it. diff --git a/base/json/json_value_serializer_unittest.cc b/base/json/json_value_serializer_unittest.cc index 1d58c61e04a7c89dad4064111047e6aca9eca992..e5cb12686102adfd1c99b9af3c1d7402050f7022 100644 --- a/base/json/json_value_serializer_unittest.cc +++ b/base/json/json_value_serializer_unittest.cc @@ -11,9 +11,7 @@ #include "base/json/json_reader.h" #include "base/json/json_string_value_serializer.h" #include "base/json/json_writer.h" -#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) #include "base/path_service.h" -#endif #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" @@ -397,8 +395,6 @@ TEST(JSONValueSerializerTest, JSONReaderComments) { ASSERT_FALSE(JSONReader::Read("/ * * / [1]")); } -#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) - class JSONFileValueSerializerTest : public testing::Test { protected: void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); } @@ -406,7 +402,7 @@ class JSONFileValueSerializerTest : public testing::Test { ScopedTempDir temp_dir_; }; -TEST_F(JSONFileValueSerializerTest, Roundtrip) { +TEST_F(JSONFileValueSerializerTest, DISABLED_Roundtrip) { FilePath original_file_path; ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &original_file_path)); original_file_path = original_file_path.AppendASCII("serializer_test.json"); @@ -449,7 +445,7 @@ TEST_F(JSONFileValueSerializerTest, Roundtrip) { EXPECT_TRUE(DeleteFile(written_file_path, false)); } -TEST_F(JSONFileValueSerializerTest, RoundtripNested) { +TEST_F(JSONFileValueSerializerTest, DISABLED_RoundtripNested) { FilePath original_file_path; ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &original_file_path)); original_file_path = @@ -475,7 +471,7 @@ TEST_F(JSONFileValueSerializerTest, RoundtripNested) { EXPECT_TRUE(DeleteFile(written_file_path, false)); } -TEST_F(JSONFileValueSerializerTest, NoWhitespace) { +TEST_F(JSONFileValueSerializerTest, DISABLED_NoWhitespace) { FilePath source_file_path; ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &source_file_path)); source_file_path = @@ -485,7 +481,6 @@ TEST_F(JSONFileValueSerializerTest, NoWhitespace) { std::unique_ptr root = deserializer.Deserialize(nullptr, nullptr); ASSERT_TRUE(root); } -#endif // !__ANDROID__ && !__ANDROID_HOST__ } // namespace diff --git a/base/memory/shared_memory_posix.cc b/base/memory/shared_memory_posix.cc index 287e55d8235200ac4fb30077b6e848caccf6388f..3443cd9b4aac59b881174f60c1268758de4c3620 100644 --- a/base/memory/shared_memory_posix.cc +++ b/base/memory/shared_memory_posix.cc @@ -15,7 +15,8 @@ #include "base/files/scoped_file.h" #include "base/logging.h" #include "base/memory/shared_memory_helper.h" -#include "base/memory/shared_memory_tracker.h" +// Unsupported in libchrome. +// #include "base/memory/shared_memory_tracker.h" #include "base/posix/eintr_wrapper.h" #include "base/posix/safe_strerror.h" #include "base/process/process_metrics.h" @@ -290,7 +291,8 @@ bool SharedMemory::MapAt(off_t offset, size_t bytes) { DCHECK_EQ(0U, reinterpret_cast(memory_) & (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); - SharedMemoryTracker::GetInstance()->IncrementMemoryUsage(*this); + // Unsupported in libchrome. + // SharedMemoryTracker::GetInstance()->IncrementMemoryUsage(*this); } else { memory_ = NULL; } @@ -303,7 +305,8 @@ bool SharedMemory::Unmap() { return false; munmap(memory_, mapped_size_); - SharedMemoryTracker::GetInstance()->DecrementMemoryUsage(*this); + // Unsupported in libchrome. + // SharedMemoryTracker::GetInstance()->DecrementMemoryUsage(*this); memory_ = NULL; mapped_size_ = 0; return true; diff --git a/base/memory/shared_memory_tracker.cc b/base/memory/shared_memory_tracker.cc deleted file mode 100644 index 8613f595336ada19357c900cf0ab9bf4d243b7f4..0000000000000000000000000000000000000000 --- a/base/memory/shared_memory_tracker.cc +++ /dev/null @@ -1,92 +0,0 @@ -// 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. - -#include "base/memory/shared_memory_tracker.h" - -#include "base/memory/shared_memory.h" -#include "base/strings/stringprintf.h" -#include "base/trace_event/memory_dump_manager.h" -#include "base/trace_event/process_memory_dump.h" - -namespace base { - -SharedMemoryTracker::Usage::Usage() = default; - -SharedMemoryTracker::Usage::Usage(const Usage& rhs) = default; - -SharedMemoryTracker::Usage::~Usage() = default; - -// static -SharedMemoryTracker* SharedMemoryTracker::GetInstance() { - static SharedMemoryTracker* instance = new SharedMemoryTracker; - return instance; -} - -void SharedMemoryTracker::IncrementMemoryUsage( - const SharedMemory& shared_memory) { - Usage usage; - // |shared_memory|'s unique ID must be generated here and it'd be too late at - // OnMemoryDump. An ID is generated with a SharedMemoryHandle, but the handle - // might already be closed at that time. Now IncrementMemoryUsage is called - // just after mmap and the handle must live then. See the discussion at - // crbug.com/604726#c30. - SharedMemory::UniqueId id; - if (!shared_memory.GetUniqueId(&id)) - return; - usage.unique_id = id; - usage.size = shared_memory.mapped_size(); - AutoLock hold(usages_lock_); - usages_[&shared_memory] = usage; -} - -void SharedMemoryTracker::DecrementMemoryUsage( - const SharedMemory& shared_memory) { - AutoLock hold(usages_lock_); - usages_.erase(&shared_memory); -} - -bool SharedMemoryTracker::OnMemoryDump(const trace_event::MemoryDumpArgs& args, - trace_event::ProcessMemoryDump* pmd) { - std::unordered_map - sizes; - { - AutoLock hold(usages_lock_); - for (const auto& usage : usages_) - sizes[usage.second.unique_id] += usage.second.size; - } - for (auto& size : sizes) { - const SharedMemory::UniqueId& id = size.first; - std::string dump_name = StringPrintf("%s/%lld.%lld", "shared_memory", - static_cast(id.first), - static_cast(id.second)); - auto guid = trace_event::MemoryAllocatorDumpGuid(dump_name); - trace_event::MemoryAllocatorDump* local_dump = - pmd->CreateAllocatorDump(dump_name); - // TODO(hajimehoshi): The size is not resident size but virtual size so far. - // Fix this to record resident size. - local_dump->AddScalar(trace_event::MemoryAllocatorDump::kNameSize, - trace_event::MemoryAllocatorDump::kUnitsBytes, - size.second); - trace_event::MemoryAllocatorDump* global_dump = - pmd->CreateSharedGlobalAllocatorDump(guid); - global_dump->AddScalar(trace_event::MemoryAllocatorDump::kNameSize, - trace_event::MemoryAllocatorDump::kUnitsBytes, - size.second); - // TOOD(hajimehoshi): Detect which the shared memory comes from browser, - // renderer or GPU process. - // TODO(hajimehoshi): Shared memory reported by GPU and discardable is - // currently double-counted. Add ownership edges to avoid this. - pmd->AddOwnershipEdge(local_dump->guid(), global_dump->guid()); - } - return true; -} - -SharedMemoryTracker::SharedMemoryTracker() { - trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( - this, "SharedMemoryTracker", nullptr); -} - -SharedMemoryTracker::~SharedMemoryTracker() = default; - -} // namespace diff --git a/base/memory/shared_memory_tracker.h b/base/memory/shared_memory_tracker.h deleted file mode 100644 index fe1a3dd392177b0c20bb5b2daf6498d85eae7e0b..0000000000000000000000000000000000000000 --- a/base/memory/shared_memory_tracker.h +++ /dev/null @@ -1,56 +0,0 @@ -// 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. - -#ifndef BASE_MEMORY_SHARED_MEMORY_TRACKER_H_ -#define BASE_MEMORY_SHARED_MEMORY_TRACKER_H_ - -#include "base/memory/shared_memory.h" -#include "base/synchronization/lock.h" -#include "base/trace_event/memory_dump_provider.h" - -namespace base { - -namespace trace_event { -class ProcessMemoryDump; -} - -// SharedMemoryTracker tracks shared memory usage. -class BASE_EXPORT SharedMemoryTracker - : public base::trace_event::MemoryDumpProvider { - public: - // Returns a singleton instance. - static SharedMemoryTracker* GetInstance(); - - // Records shared memory usage on mapping. - void IncrementMemoryUsage(const SharedMemory& shared_memory); - - // Records shared memory usage on unmapping. - void DecrementMemoryUsage(const SharedMemory& shared_memory); - - private: - struct Usage { - Usage(); - Usage(const Usage& rhs); - ~Usage(); - SharedMemory::UniqueId unique_id; - size_t size; - }; - - SharedMemoryTracker(); - ~SharedMemoryTracker() override; - - // base::trace_event::MemoryDumpProvider implementation. - bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, - base::trace_event::ProcessMemoryDump* pmd) override; - - // Used to lock when |usages_| is modified or read. - Lock usages_lock_; - std::unordered_map usages_; - - DISALLOW_COPY_AND_ASSIGN(SharedMemoryTracker); -}; - -} // namespace base - -#endif // BASE_MEMORY_SHARED_MEMORY_TRACKER_H_ diff --git a/base/os_compat_android.cc b/base/os_compat_android.cc deleted file mode 100644 index 1eb6536bb1eee577b1ec09ef7f6011b642de3031..0000000000000000000000000000000000000000 --- a/base/os_compat_android.cc +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) 2012 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. - -#include "base/os_compat_android.h" - -#include -#include -#include -#include -#include -#include - -#if !defined(__LP64__) -#include -#endif - -#include "base/rand_util.h" -#include "base/strings/string_piece.h" -#include "base/strings/stringprintf.h" - -extern "C" { -// There is no futimes() avaiable in Bionic, so we provide our own -// implementation until it is there. -int futimes(int fd, const struct timeval tv[2]) { - if (tv == NULL) - return syscall(__NR_utimensat, fd, NULL, NULL, 0); - - if (tv[0].tv_usec < 0 || tv[0].tv_usec >= 1000000 || - tv[1].tv_usec < 0 || tv[1].tv_usec >= 1000000) { - errno = EINVAL; - return -1; - } - - // Convert timeval to timespec. - struct timespec ts[2]; - ts[0].tv_sec = tv[0].tv_sec; - ts[0].tv_nsec = tv[0].tv_usec * 1000; - ts[1].tv_sec = tv[1].tv_sec; - ts[1].tv_nsec = tv[1].tv_usec * 1000; - return syscall(__NR_utimensat, fd, NULL, ts, 0); -} - -#if !defined(__LP64__) -// 32-bit Android has only timegm64() and not timegm(). -// We replicate the behaviour of timegm() when the result overflows time_t. -time_t timegm(struct tm* const t) { - // time_t is signed on Android. - static const time_t kTimeMax = ~(1L << (sizeof(time_t) * CHAR_BIT - 1)); - static const time_t kTimeMin = (1L << (sizeof(time_t) * CHAR_BIT - 1)); - time64_t result = timegm64(t); - if (result < kTimeMin || result > kTimeMax) - return -1; - return result; -} -#endif - -// The following is only needed when building with GCC 4.6 or higher -// (i.e. not with Android GCC 4.4.3, nor with Clang). -// -// GCC is now capable of optimizing successive calls to sin() and cos() into -// a single call to sincos(). This means that source code that looks like: -// -// double c, s; -// c = cos(angle); -// s = sin(angle); -// -// Will generate machine code that looks like: -// -// double c, s; -// sincos(angle, &s, &c); -// -// Unfortunately, sincos() and friends are not part of the Android libm.so -// library provided by the NDK for API level 9. When the optimization kicks -// in, it makes the final build fail with a puzzling message (puzzling -// because 'sincos' doesn't appear anywhere in the sources!). -// -// To solve this, we provide our own implementation of the sincos() function -// and related friends. Note that we must also explicitely tell GCC to disable -// optimizations when generating these. Otherwise, the generated machine code -// for each function would simply end up calling itself, resulting in a -// runtime crash due to stack overflow. -// -#if defined(__GNUC__) && !defined(__clang__) && \ - !defined(ANDROID_SINCOS_PROVIDED) - -// For the record, Clang does not support the 'optimize' attribute. -// In the unlikely event that it begins performing this optimization too, -// we'll have to find a different way to achieve this. NOTE: Tested with O1 -// which still performs the optimization. -// -#define GCC_NO_OPTIMIZE __attribute__((optimize("O0"))) - -GCC_NO_OPTIMIZE -void sincos(double angle, double* s, double *c) { - *c = cos(angle); - *s = sin(angle); -} - -GCC_NO_OPTIMIZE -void sincosf(float angle, float* s, float* c) { - *c = cosf(angle); - *s = sinf(angle); -} - -#endif // __GNUC__ && !__clang__ - -// An implementation of mkdtemp, since it is not exposed by the NDK -// for native API level 9 that we target. -// -// For any changes in the mkdtemp function, you should manually run the unittest -// OsCompatAndroidTest.DISABLED_TestMkdTemp in your local machine to check if it -// passes. Please don't enable it, since it creates a directory and may be -// source of flakyness. -char* mkdtemp(char* path) { - if (path == NULL) { - errno = EINVAL; - return NULL; - } - - const int path_len = strlen(path); - - // The last six characters of 'path' must be XXXXXX. - const base::StringPiece kSuffix("XXXXXX"); - const int kSuffixLen = kSuffix.length(); - if (!base::StringPiece(path, path_len).ends_with(kSuffix)) { - errno = EINVAL; - return NULL; - } - - // If the path contains a directory, as in /tmp/foo/XXXXXXXX, make sure - // that /tmp/foo exists, otherwise we're going to loop a really long - // time for nothing below - char* dirsep = strrchr(path, '/'); - if (dirsep != NULL) { - struct stat st; - int ret; - - *dirsep = '\0'; // Terminating directory path temporarily - - ret = stat(path, &st); - - *dirsep = '/'; // Restoring directory separator - if (ret < 0) // Directory probably does not exist - return NULL; - if (!S_ISDIR(st.st_mode)) { // Not a directory - errno = ENOTDIR; - return NULL; - } - } - - // Max number of tries using different random suffixes. - const int kMaxTries = 100; - - // Now loop until we CAN create a directory by that name or we reach the max - // number of tries. - for (int i = 0; i < kMaxTries; ++i) { - // Fill the suffix XXXXXX with a random string composed of a-z chars. - for (int pos = 0; pos < kSuffixLen; ++pos) { - char rand_char = static_cast(base::RandInt('a', 'z')); - path[path_len - kSuffixLen + pos] = rand_char; - } - if (mkdir(path, 0700) == 0) { - // We just created the directory succesfully. - return path; - } - if (errno != EEXIST) { - // The directory doesn't exist, but an error occured - return NULL; - } - } - - // We reached the max number of tries. - return NULL; -} - -} // extern "C" diff --git a/base/os_compat_android.h b/base/os_compat_android.h deleted file mode 100644 index 0f2544496d6d8e7898e3d697131e2f35f57c4fe6..0000000000000000000000000000000000000000 --- a/base/os_compat_android.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2012 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 BASE_OS_COMPAT_ANDROID_H_ -#define BASE_OS_COMPAT_ANDROID_H_ - -#include -#include -#include - -// Not implemented in Bionic. -extern "C" int futimes(int fd, const struct timeval tv[2]); - -// Not exposed or implemented in Bionic. -extern "C" char* mkdtemp(char* path); - -// Android has no timegm(). -extern "C" time_t timegm(struct tm* const t); - -// The lockf() function is not available on Android; we translate to flock(). -#define F_LOCK LOCK_EX -#define F_ULOCK LOCK_UN -inline int lockf(int fd, int cmd, off_t ignored_len) { - return flock(fd, cmd); -} - -#endif // BASE_OS_COMPAT_ANDROID_H_ diff --git a/base/os_compat_android_unittest.cc b/base/os_compat_android_unittest.cc deleted file mode 100644 index 7fbdc6dace63d6ce1dc7f53c3afcd13aec860c18..0000000000000000000000000000000000000000 --- a/base/os_compat_android_unittest.cc +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2012 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. - -#include "base/os_compat_android.h" - -#include "base/files/file_util.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { - -typedef testing::Test OsCompatAndroidTest; - -// Keep this Unittest DISABLED_ , because it actually creates a directory in the -// device and it may be source of flakyness. For any changes in the mkdtemp -// function, you should run this unittest in your local machine to check if it -// passes. -TEST_F(OsCompatAndroidTest, DISABLED_TestMkdTemp) { - FilePath tmp_dir; - EXPECT_TRUE(base::GetTempDir(&tmp_dir)); - - // Not six XXXXXX at the suffix of the path. - FilePath sub_dir = tmp_dir.Append("XX"); - std::string sub_dir_string = sub_dir.value(); - // this should be OK since mkdtemp just replaces characters in place - char* buffer = const_cast(sub_dir_string.c_str()); - EXPECT_EQ(NULL, mkdtemp(buffer)); - - // Directory does not exist - char invalid_path2[] = "doesntoexist/foobarXXXXXX"; - EXPECT_EQ(NULL, mkdtemp(invalid_path2)); - - // Successfully create a tmp dir. - FilePath sub_dir2 = tmp_dir.Append("XXXXXX"); - std::string sub_dir2_string = sub_dir2.value(); - // this should be OK since mkdtemp just replaces characters in place - char* buffer2 = const_cast(sub_dir2_string.c_str()); - EXPECT_TRUE(mkdtemp(buffer2) != NULL); -} - -} // namespace base diff --git a/base/path_service.cc b/base/path_service.cc new file mode 100644 index 0000000000000000000000000000000000000000..1b9d3949300bb9c3286f8f365c8588a4540936fa --- /dev/null +++ b/base/path_service.cc @@ -0,0 +1,329 @@ +// Copyright (c) 2012 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. + +#include "base/path_service.h" + +#if defined(OS_WIN) +#include +#include +#include +#endif + +#include "base/containers/hash_tables.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "build/build_config.h" + +namespace base { + +bool PathProvider(int key, FilePath* result); + +#if defined(OS_WIN) +bool PathProviderWin(int key, FilePath* result); +#elif defined(OS_MACOSX) +bool PathProviderMac(int key, FilePath* result); +#elif defined(OS_ANDROID) +bool PathProviderAndroid(int key, FilePath* result); +#elif defined(OS_POSIX) +// PathProviderPosix is the default path provider on POSIX OSes other than +// Mac and Android. +bool PathProviderPosix(int key, FilePath* result); +#endif + +namespace { + +typedef hash_map PathMap; + +// We keep a linked list of providers. In a debug build we ensure that no two +// providers claim overlapping keys. +struct Provider { + PathService::ProviderFunc func; + struct Provider* next; +#ifndef NDEBUG + int key_start; + int key_end; +#endif + bool is_static; +}; + +Provider base_provider = { + PathProvider, + NULL, +#ifndef NDEBUG + PATH_START, + PATH_END, +#endif + true +}; + +#if defined(OS_WIN) +Provider base_provider_win = { + PathProviderWin, + &base_provider, +#ifndef NDEBUG + PATH_WIN_START, + PATH_WIN_END, +#endif + true +}; +#endif + +#if defined(OS_MACOSX) +Provider base_provider_mac = { + PathProviderMac, + &base_provider, +#ifndef NDEBUG + PATH_MAC_START, + PATH_MAC_END, +#endif + true +}; +#endif + +#if defined(OS_ANDROID) +Provider base_provider_android = { + PathProviderAndroid, + &base_provider, +#ifndef NDEBUG + PATH_ANDROID_START, + PATH_ANDROID_END, +#endif + true +}; +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) +Provider base_provider_posix = { + PathProviderPosix, + &base_provider, +#ifndef NDEBUG + PATH_POSIX_START, + PATH_POSIX_END, +#endif + true +}; +#endif + + +struct PathData { + Lock lock; + PathMap cache; // Cache mappings from path key to path value. + PathMap overrides; // Track path overrides. + Provider* providers; // Linked list of path service providers. + bool cache_disabled; // Don't use cache if true; + + PathData() : cache_disabled(false) { +#if defined(OS_WIN) + providers = &base_provider_win; +#elif defined(OS_MACOSX) + providers = &base_provider_mac; +#elif defined(OS_ANDROID) + providers = &base_provider_android; +#elif defined(OS_POSIX) + providers = &base_provider_posix; +#endif + } +}; + +static PathData* GetPathData() { + static auto* path_data = new PathData(); + return path_data; +} + +// Tries to find |key| in the cache. |path_data| should be locked by the caller! +bool LockedGetFromCache(int key, const PathData* path_data, FilePath* result) { + if (path_data->cache_disabled) + return false; + // check for a cached version + PathMap::const_iterator it = path_data->cache.find(key); + if (it != path_data->cache.end()) { + *result = it->second; + return true; + } + return false; +} + +// Tries to find |key| in the overrides map. |path_data| should be locked by the +// caller! +bool LockedGetFromOverrides(int key, PathData* path_data, FilePath* result) { + // check for an overridden version. + PathMap::const_iterator it = path_data->overrides.find(key); + if (it != path_data->overrides.end()) { + if (!path_data->cache_disabled) + path_data->cache[key] = it->second; + *result = it->second; + return true; + } + return false; +} + +} // namespace + +// TODO(brettw): this function does not handle long paths (filename > MAX_PATH) +// characters). This isn't supported very well by Windows right now, so it is +// moot, but we should keep this in mind for the future. +// static +bool PathService::Get(int key, FilePath* result) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK(result); + DCHECK_GE(key, DIR_CURRENT); + + // special case the current directory because it can never be cached + if (key == DIR_CURRENT) + return GetCurrentDirectory(result); + + Provider* provider = NULL; + { + AutoLock scoped_lock(path_data->lock); + if (LockedGetFromCache(key, path_data, result)) + return true; + + if (LockedGetFromOverrides(key, path_data, result)) + return true; + + // Get the beginning of the list while it is still locked. + provider = path_data->providers; + } + + FilePath path; + + // Iterating does not need the lock because only the list head might be + // modified on another thread. + while (provider) { + if (provider->func(key, &path)) + break; + DCHECK(path.empty()) << "provider should not have modified path"; + provider = provider->next; + } + + if (path.empty()) + return false; + + if (path.ReferencesParent()) { + // Make sure path service never returns a path with ".." in it. + path = MakeAbsoluteFilePath(path); + if (path.empty()) + return false; + } + *result = path; + + AutoLock scoped_lock(path_data->lock); + if (!path_data->cache_disabled) + path_data->cache[key] = path; + + return true; +} + +// static +bool PathService::Override(int key, const FilePath& path) { + // Just call the full function with true for the value of |create|, and + // assume that |path| may not be absolute yet. + return OverrideAndCreateIfNeeded(key, path, false, true); +} + +// static +bool PathService::OverrideAndCreateIfNeeded(int key, + const FilePath& path, + bool is_absolute, + bool create) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK_GT(key, DIR_CURRENT) << "invalid path key"; + + FilePath file_path = path; + + // For some locations this will fail if called from inside the sandbox there- + // fore we protect this call with a flag. + if (create) { + // Make sure the directory exists. We need to do this before we translate + // this to the absolute path because on POSIX, MakeAbsoluteFilePath fails + // if called on a non-existent path. + if (!PathExists(file_path) && !CreateDirectory(file_path)) + return false; + } + + // We need to have an absolute path. + if (!is_absolute) { + file_path = MakeAbsoluteFilePath(file_path); + if (file_path.empty()) + return false; + } + DCHECK(file_path.IsAbsolute()); + + AutoLock scoped_lock(path_data->lock); + + // Clear the cache now. Some of its entries could have depended + // on the value we are overriding, and are now out of sync with reality. + path_data->cache.clear(); + + path_data->overrides[key] = file_path; + + return true; +} + +// static +bool PathService::RemoveOverride(int key) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + + AutoLock scoped_lock(path_data->lock); + + if (path_data->overrides.find(key) == path_data->overrides.end()) + return false; + + // Clear the cache now. Some of its entries could have depended on the value + // we are going to remove, and are now out of sync. + path_data->cache.clear(); + + path_data->overrides.erase(key); + + return true; +} + +// static +void PathService::RegisterProvider(ProviderFunc func, int key_start, + int key_end) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK_GT(key_end, key_start); + + Provider* p; + + p = new Provider; + p->is_static = false; + p->func = func; +#ifndef NDEBUG + p->key_start = key_start; + p->key_end = key_end; +#endif + + AutoLock scoped_lock(path_data->lock); + +#ifndef NDEBUG + Provider *iter = path_data->providers; + while (iter) { + DCHECK(key_start >= iter->key_end || key_end <= iter->key_start) << + "path provider collision"; + iter = iter->next; + } +#endif + + p->next = path_data->providers; + path_data->providers = p; +} + +// static +void PathService::DisableCache() { + PathData* path_data = GetPathData(); + DCHECK(path_data); + + AutoLock scoped_lock(path_data->lock); + path_data->cache.clear(); + path_data->cache_disabled = true; +} + +} // namespace base diff --git a/base/path_service.h b/base/path_service.h new file mode 100644 index 0000000000000000000000000000000000000000..c7f1abe71498d149accb774e3f30966853592a2f --- /dev/null +++ b/base/path_service.h @@ -0,0 +1,97 @@ +// Copyright (c) 2012 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 BASE_PATH_SERVICE_H_ +#define BASE_PATH_SERVICE_H_ + +#include + +#include "base/base_export.h" +#include "base/base_paths.h" +#include "base/gtest_prod_util.h" +#include "build/build_config.h" + +namespace base { + +class FilePath; +class ScopedPathOverride; + +// The path service is a global table mapping keys to file system paths. It is +// OK to use this service from multiple threads. +// +class BASE_EXPORT PathService { + public: + // Retrieves a path to a special directory or file and places it into the + // string pointed to by 'path'. If you ask for a directory it is guaranteed + // to NOT have a path separator at the end. For example, "c:\windows\temp" + // Directories are also guaranteed to exist when this function succeeds. + // + // Returns true if the directory or file was successfully retrieved. On + // failure, 'path' will not be changed. + static bool Get(int key, FilePath* path); + + // Overrides the path to a special directory or file. This cannot be used to + // change the value of DIR_CURRENT, but that should be obvious. Also, if the + // path specifies a directory that does not exist, the directory will be + // created by this method. This method returns true if successful. + // + // If the given path is relative, then it will be resolved against + // DIR_CURRENT. + // + // WARNING: Consumers of PathService::Get may expect paths to be constant + // over the lifetime of the app, so this method should be used with caution. + // + // Unit tests generally should use ScopedPathOverride instead. Overrides from + // one test should not carry over to another. + static bool Override(int key, const FilePath& path); + + // This function does the same as PathService::Override but it takes extra + // parameters: + // - |is_absolute| indicates that |path| has already been expanded into an + // absolute path, otherwise MakeAbsoluteFilePath() will be used. This is + // useful to override paths that may not exist yet, since MakeAbsoluteFilePath + // fails for those. Note that MakeAbsoluteFilePath also expands symbolic + // links, even if path.IsAbsolute() is already true. + // - |create| guides whether the directory to be overriden must + // be created in case it doesn't exist already. + static bool OverrideAndCreateIfNeeded(int key, + const FilePath& path, + bool is_absolute, + bool create); + + // To extend the set of supported keys, you can register a path provider, + // which is just a function mirroring PathService::Get. The ProviderFunc + // returns false if it cannot provide a non-empty path for the given key. + // Otherwise, true is returned. + // + // WARNING: This function could be called on any thread from which the + // PathService is used, so a the ProviderFunc MUST BE THREADSAFE. + // + typedef bool (*ProviderFunc)(int, FilePath*); + + // Call to register a path provider. You must specify the range "[key_start, + // key_end)" of supported path keys. + static void RegisterProvider(ProviderFunc provider, + int key_start, + int key_end); + + // Disable internal cache. + static void DisableCache(); + + private: + friend class ScopedPathOverride; + FRIEND_TEST_ALL_PREFIXES(PathServiceTest, RemoveOverride); + + // Removes an override for a special directory or file. Returns true if there + // was an override to remove or false if none was present. + // NOTE: This function is intended to be used by tests only! + static bool RemoveOverride(int key); +}; + +} // namespace base + +// TODO(brettw) Convert all callers to using the base namespace and remove this. +using base::PathService; + +#endif // BASE_PATH_SERVICE_H_ diff --git a/base/process/process_metrics_unittest.cc b/base/process/process_metrics_unittest.cc index 288cde9fc6c8c0f4abc92e4c836b8701399318f1..d9c8aaa2febb571691decc8fc607740b8f5949ae 100644 --- a/base/process/process_metrics_unittest.cc +++ b/base/process/process_metrics_unittest.cc @@ -549,7 +549,7 @@ MULTIPROCESS_TEST_MAIN(ChildMain) { } // namespace -// Arc++ note: don't compile as SpawnMultiProcessTestChild brings in a lot of +// ARC note: don't compile as SpawnMultiProcessTestChild brings in a lot of // extra dependency. #if !defined(OS_ANDROID) && !defined(__ANDROID__) && !defined(__ANDROID_HOST__) TEST(ProcessMetricsTest, GetOpenFdCount) { diff --git a/base/profiler/scoped_profile.cc b/base/profiler/scoped_profile.cc deleted file mode 100644 index f06a8c6f5dd68fafe34b90627acf8808499c7552..0000000000000000000000000000000000000000 --- a/base/profiler/scoped_profile.cc +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2011 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. - -#include "base/profiler/scoped_profile.h" - -#include "base/location.h" -#include "base/tracked_objects.h" - - -namespace tracked_objects { - - -ScopedProfile::ScopedProfile(const Location& location, Mode mode) - : birth_(NULL) { - if (mode == DISABLED) - return; - - birth_ = ThreadData::TallyABirthIfActive(location); - if (!birth_) - return; - - stopwatch_.Start(); -} - -ScopedProfile::~ScopedProfile() { - if (!birth_) - return; - - stopwatch_.Stop(); - ThreadData::TallyRunInAScopedRegionIfTracking(birth_, stopwatch_); -} - -} // namespace tracked_objects diff --git a/base/profiler/scoped_profile.h b/base/profiler/scoped_profile.h deleted file mode 100644 index 4df6a1bc024483f22c95f1d438dcf239014d874c..0000000000000000000000000000000000000000 --- a/base/profiler/scoped_profile.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2011 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 BASE_PROFILER_SCOPED_PROFILE_H_ -#define BASE_PROFILER_SCOPED_PROFILE_H_ - -//------------------------------------------------------------------------------ -// ScopedProfile provides basic helper functions for profiling a short -// region of code within a scope. It is separate from the related ThreadData -// class so that it can be included without much other cruft, and provide the -// macros listed below. - -#include "base/base_export.h" -#include "base/location.h" -#include "base/macros.h" -#include "base/profiler/tracked_time.h" -#include "base/trace_event/heap_profiler.h" -#include "base/tracked_objects.h" - -// Two level indirection is required for correct macro substitution. -#define PASTE_COUNTER_ON_NAME2(name, counter) name##counter -#define PASTE_COUNTER_ON_NAME(name, counter) \ - PASTE_COUNTER_ON_NAME2(name, counter) - -#define COUNTER_BASED_VARIABLE_NAME_FOR_PROFILING \ - PASTE_COUNTER_ON_NAME(some_profiler_variable_, __COUNTER__) - -// Defines the containing scope as a profiled region. This allows developers to -// profile their code and see results on their about:profiler page, as well as -// on the UMA dashboard and heap profiler. -#define TRACK_RUN_IN_THIS_SCOPED_REGION(dispatch_function_name) \ - const ::tracked_objects::Location& location = \ - FROM_HERE_WITH_EXPLICIT_FUNCTION(#dispatch_function_name); \ - TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION \ - COUNTER_BASED_VARIABLE_NAME_FOR_PROFILING(location.file_name()); \ - ::tracked_objects::ScopedProfile COUNTER_BASED_VARIABLE_NAME_FOR_PROFILING( \ - location, ::tracked_objects::ScopedProfile::ENABLED) - -// Same as TRACK_RUN_IN_THIS_SCOPED_REGION except that there's an extra param -// which is concatenated with the function name for better filtering. -#define TRACK_SCOPED_REGION(category_name, dispatch_function_name) \ - const ::tracked_objects::Location& location = \ - FROM_HERE_WITH_EXPLICIT_FUNCTION("[" category_name \ - "]" dispatch_function_name); \ - TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION \ - COUNTER_BASED_VARIABLE_NAME_FOR_PROFILING(location.file_name()); \ - ::tracked_objects::ScopedProfile COUNTER_BASED_VARIABLE_NAME_FOR_PROFILING( \ - location, ::tracked_objects::ScopedProfile::ENABLED) - -namespace tracked_objects { -class Births; - -class BASE_EXPORT ScopedProfile { - public: - // Mode of operation. Specifies whether ScopedProfile should be a no-op or - // needs to create and tally a task. - enum Mode { - DISABLED, // Do nothing. - ENABLED // Create and tally a task. - }; - - ScopedProfile(const Location& location, Mode mode); - ~ScopedProfile(); - - private: - Births* birth_; // Place in code where tracking started. - TaskStopwatch stopwatch_; - - DISALLOW_COPY_AND_ASSIGN(ScopedProfile); -}; - -} // namespace tracked_objects - -#endif // BASE_PROFILER_SCOPED_PROFILE_H_ diff --git a/base/profiler/scoped_tracker.cc b/base/profiler/scoped_tracker.cc deleted file mode 100644 index d15b7de6dcd2d3d6763d6395f2f471b79c210092..0000000000000000000000000000000000000000 --- a/base/profiler/scoped_tracker.cc +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2014 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. - -#include "base/profiler/scoped_tracker.h" - -#include "base/bind.h" - -namespace tracked_objects { - -namespace { - -ScopedProfile::Mode g_scoped_profile_mode = ScopedProfile::DISABLED; - -} // namespace - -// static -void ScopedTracker::Enable() { - g_scoped_profile_mode = ScopedProfile::ENABLED; -} - -ScopedTracker::ScopedTracker(const Location& location) - : scoped_profile_(location, g_scoped_profile_mode) { -} - -} // namespace tracked_objects diff --git a/base/profiler/scoped_tracker.h b/base/profiler/scoped_tracker.h deleted file mode 100644 index a61de9115cfe36aadf9cc211f1dbd5e9ae843ea9..0000000000000000000000000000000000000000 --- a/base/profiler/scoped_tracker.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2014 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 BASE_PROFILER_SCOPED_TRACKER_H_ -#define BASE_PROFILER_SCOPED_TRACKER_H_ - -//------------------------------------------------------------------------------ -// Utilities for temporarily instrumenting code to dig into issues that were -// found using profiler data. - -#include "base/base_export.h" -#include "base/bind.h" -#include "base/callback_forward.h" -#include "base/location.h" -#include "base/macros.h" -#include "base/profiler/scoped_profile.h" - -namespace tracked_objects { - -// ScopedTracker instruments a region within the code if the instrumentation is -// enabled. It can be used, for example, to find out if a source of jankiness is -// inside the instrumented code region. -// Details: -// 1. This class creates a task (like ones created by PostTask calls or IPC -// message handlers). This task can be seen in chrome://profiler and is sent as -// a part of profiler data to the UMA server. See profiler_event.proto. -// 2. That task's lifetime is same as the lifetime of the ScopedTracker -// instance. -// 3. The execution time associated with the task is the wallclock time between -// its constructor and destructor, minus wallclock times of directly nested -// tasks. -// 4. Task creation that this class utilizes is highly optimized. -// 5. The class doesn't create a task unless this was enabled for the current -// process. Search for ScopedTracker::Enable for the current list of processes -// and channels where it's activated. -// 6. The class is designed for temporarily instrumenting code to find -// performance problems, after which the instrumentation must be removed. -class BASE_EXPORT ScopedTracker { - public: - ScopedTracker(const Location& location); - - // Enables instrumentation for the remainder of the current process' life. If - // this function is not called, all profiler instrumentations are no-ops. - static void Enable(); - - // Augments a |callback| with provided |location|. This is useful for - // instrumenting cases when we know that a jank is in a callback and there are - // many possible callbacks, but they come from a relatively small number of - // places. We can instrument these few places and at least know which one - // passes the janky callback. - template - static base::Callback TrackCallback( - const Location& location, - const base::Callback& callback) { - return base::Bind(&ScopedTracker::ExecuteAndTrackCallback, location, - callback); - } - - private: - // Executes |callback|, augmenting it with provided |location|. - template - static void ExecuteAndTrackCallback(const Location& location, - const base::Callback& callback, - P1 p1) { - ScopedTracker tracking_profile(location); - callback.Run(p1); - } - - const ScopedProfile scoped_profile_; - - DISALLOW_COPY_AND_ASSIGN(ScopedTracker); -}; - -} // namespace tracked_objects - -#endif // BASE_PROFILER_SCOPED_TRACKER_H_ diff --git a/base/test/multiprocess_test.cc b/base/test/multiprocess_test.cc index c8fd3eddad60b966c3e55a12dd5c3e3cecf998ce..3e09e693996a5f7871e21d080c986908cbc92ee2 100644 --- a/base/test/multiprocess_test.cc +++ b/base/test/multiprocess_test.cc @@ -54,7 +54,7 @@ CommandLine GetMultiProcessTestChildBaseCommandLine() { MultiProcessTest::MultiProcessTest() { } -// Don't compile on Arc++. +// Don't compile on ARC. #if 0 SpawnChildResult MultiProcessTest::SpawnChild(const std::string& procname) { LaunchOptions options; diff --git a/base/test/test_pending_task.cc b/base/test/test_pending_task.cc index 3f71a9988fc0074db3c149f0f8a7fdafb0123f0c..fcc48a89809c6f291dc7a242186eb57c8880093c 100644 --- a/base/test/test_pending_task.cc +++ b/base/test/test_pending_task.cc @@ -38,6 +38,8 @@ bool TestPendingTask::ShouldRunBefore(const TestPendingTask& other) const { TestPendingTask::~TestPendingTask() {} +// Unsupported in libchrome. +#if 0 void TestPendingTask::AsValueInto(base::trace_event::TracedValue* state) const { state->SetInteger("run_at", GetTimeToRun().ToInternalValue()); state->SetString("posting_function", location.ToString()); @@ -61,10 +63,14 @@ TestPendingTask::AsValue() const { AsValueInto(state.get()); return std::move(state); } +#endif std::string TestPendingTask::ToString() const { std::string output("TestPendingTask("); +// Unsupported in libchrome. +#if 0 AsValue()->AppendAsTraceFormat(&output); +#endif output += ")"; return output; } diff --git a/base/test/test_pending_task.h b/base/test/test_pending_task.h index 52ca592f25c35e524ae5715dc33f75f360d60aba..f8e8c798b871ec21d914c5c2081f0874f04a0509 100644 --- a/base/test/test_pending_task.h +++ b/base/test/test_pending_task.h @@ -10,7 +10,8 @@ #include "base/callback.h" #include "base/location.h" #include "base/time/time.h" -#include "base/trace_event/trace_event_argument.h" +// Unsupported in libchrome. +// #include "base/trace_event/trace_event_argument.h" namespace base { @@ -58,10 +59,13 @@ struct TestPendingTask { TimeDelta delay; TestNestability nestability; +// Unsupported in libchrome. +#if 0 // Functions for using test pending task with tracing, useful in unit // testing. void AsValueInto(base::trace_event::TracedValue* state) const; std::unique_ptr AsValue() const; +#endif std::string ToString() const; private: diff --git a/base/test/trace_event_analyzer.cc b/base/test/trace_event_analyzer.cc deleted file mode 100644 index e61337cccbdc259626ce17fec19d71e8c7637a6c..0000000000000000000000000000000000000000 --- a/base/test/trace_event_analyzer.cc +++ /dev/null @@ -1,1027 +0,0 @@ -// Copyright (c) 2012 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. - -#include "base/test/trace_event_analyzer.h" - -#include - -#include -#include -#include - -#include "base/json/json_reader.h" -#include "base/strings/pattern.h" -#include "base/values.h" - -namespace trace_analyzer { - -// TraceEvent - -TraceEvent::TraceEvent() - : thread(0, 0), - timestamp(0), - duration(0), - phase(TRACE_EVENT_PHASE_BEGIN), - other_event(NULL) { -} - -TraceEvent::TraceEvent(TraceEvent&& other) = default; - -TraceEvent::~TraceEvent() { -} - -TraceEvent& TraceEvent::operator=(TraceEvent&& rhs) = default; - -bool TraceEvent::SetFromJSON(const base::Value* event_value) { - if (event_value->GetType() != base::Value::Type::DICTIONARY) { - LOG(ERROR) << "Value must be Type::DICTIONARY"; - return false; - } - const base::DictionaryValue* dictionary = - static_cast(event_value); - - std::string phase_str; - const base::DictionaryValue* args = NULL; - - if (!dictionary->GetString("ph", &phase_str)) { - LOG(ERROR) << "ph is missing from TraceEvent JSON"; - return false; - } - - phase = *phase_str.data(); - - bool may_have_duration = (phase == TRACE_EVENT_PHASE_COMPLETE); - bool require_origin = (phase != TRACE_EVENT_PHASE_METADATA); - bool require_id = (phase == TRACE_EVENT_PHASE_ASYNC_BEGIN || - phase == TRACE_EVENT_PHASE_ASYNC_STEP_INTO || - phase == TRACE_EVENT_PHASE_ASYNC_STEP_PAST || - phase == TRACE_EVENT_PHASE_MEMORY_DUMP || - phase == TRACE_EVENT_PHASE_ENTER_CONTEXT || - phase == TRACE_EVENT_PHASE_LEAVE_CONTEXT || - phase == TRACE_EVENT_PHASE_CREATE_OBJECT || - phase == TRACE_EVENT_PHASE_DELETE_OBJECT || - phase == TRACE_EVENT_PHASE_SNAPSHOT_OBJECT || - phase == TRACE_EVENT_PHASE_ASYNC_END); - - if (require_origin && !dictionary->GetInteger("pid", &thread.process_id)) { - LOG(ERROR) << "pid is missing from TraceEvent JSON"; - return false; - } - if (require_origin && !dictionary->GetInteger("tid", &thread.thread_id)) { - LOG(ERROR) << "tid is missing from TraceEvent JSON"; - return false; - } - if (require_origin && !dictionary->GetDouble("ts", ×tamp)) { - LOG(ERROR) << "ts is missing from TraceEvent JSON"; - return false; - } - if (may_have_duration) { - dictionary->GetDouble("dur", &duration); - } - if (!dictionary->GetString("cat", &category)) { - LOG(ERROR) << "cat is missing from TraceEvent JSON"; - return false; - } - if (!dictionary->GetString("name", &name)) { - LOG(ERROR) << "name is missing from TraceEvent JSON"; - return false; - } - if (!dictionary->GetDictionary("args", &args)) { - LOG(ERROR) << "args is missing from TraceEvent JSON"; - return false; - } - if (require_id && !dictionary->GetString("id", &id)) { - LOG(ERROR) << "id is missing from ASYNC_BEGIN/ASYNC_END TraceEvent JSON"; - return false; - } - - // For each argument, copy the type and create a trace_analyzer::TraceValue. - for (base::DictionaryValue::Iterator it(*args); !it.IsAtEnd(); - it.Advance()) { - std::string str; - bool boolean = false; - int int_num = 0; - double double_num = 0.0; - if (it.value().GetAsString(&str)) { - arg_strings[it.key()] = str; - } else if (it.value().GetAsInteger(&int_num)) { - arg_numbers[it.key()] = static_cast(int_num); - } else if (it.value().GetAsBoolean(&boolean)) { - arg_numbers[it.key()] = static_cast(boolean ? 1 : 0); - } else if (it.value().GetAsDouble(&double_num)) { - arg_numbers[it.key()] = double_num; - } - // Record all arguments as values. - arg_values[it.key()] = it.value().CreateDeepCopy(); - } - - return true; -} - -double TraceEvent::GetAbsTimeToOtherEvent() const { - return fabs(other_event->timestamp - timestamp); -} - -bool TraceEvent::GetArgAsString(const std::string& name, - std::string* arg) const { - const auto it = arg_strings.find(name); - if (it != arg_strings.end()) { - *arg = it->second; - return true; - } - return false; -} - -bool TraceEvent::GetArgAsNumber(const std::string& name, - double* arg) const { - const auto it = arg_numbers.find(name); - if (it != arg_numbers.end()) { - *arg = it->second; - return true; - } - return false; -} - -bool TraceEvent::GetArgAsValue(const std::string& name, - std::unique_ptr* arg) const { - const auto it = arg_values.find(name); - if (it != arg_values.end()) { - *arg = it->second->CreateDeepCopy(); - return true; - } - return false; -} - -bool TraceEvent::HasStringArg(const std::string& name) const { - return (arg_strings.find(name) != arg_strings.end()); -} - -bool TraceEvent::HasNumberArg(const std::string& name) const { - return (arg_numbers.find(name) != arg_numbers.end()); -} - -bool TraceEvent::HasArg(const std::string& name) const { - return (arg_values.find(name) != arg_values.end()); -} - -std::string TraceEvent::GetKnownArgAsString(const std::string& name) const { - std::string arg_string; - bool result = GetArgAsString(name, &arg_string); - DCHECK(result); - return arg_string; -} - -double TraceEvent::GetKnownArgAsDouble(const std::string& name) const { - double arg_double = 0; - bool result = GetArgAsNumber(name, &arg_double); - DCHECK(result); - return arg_double; -} - -int TraceEvent::GetKnownArgAsInt(const std::string& name) const { - double arg_double = 0; - bool result = GetArgAsNumber(name, &arg_double); - DCHECK(result); - return static_cast(arg_double); -} - -bool TraceEvent::GetKnownArgAsBool(const std::string& name) const { - double arg_double = 0; - bool result = GetArgAsNumber(name, &arg_double); - DCHECK(result); - return (arg_double != 0.0); -} - -std::unique_ptr TraceEvent::GetKnownArgAsValue( - const std::string& name) const { - std::unique_ptr arg_value; - bool result = GetArgAsValue(name, &arg_value); - DCHECK(result); - return arg_value; -} - -// QueryNode - -QueryNode::QueryNode(const Query& query) : query_(query) { -} - -QueryNode::~QueryNode() { -} - -// Query - -Query::Query(TraceEventMember member) - : type_(QUERY_EVENT_MEMBER), - operator_(OP_INVALID), - member_(member), - number_(0), - is_pattern_(false) { -} - -Query::Query(TraceEventMember member, const std::string& arg_name) - : type_(QUERY_EVENT_MEMBER), - operator_(OP_INVALID), - member_(member), - number_(0), - string_(arg_name), - is_pattern_(false) { -} - -Query::Query(const Query& query) - : type_(query.type_), - operator_(query.operator_), - left_(query.left_), - right_(query.right_), - member_(query.member_), - number_(query.number_), - string_(query.string_), - is_pattern_(query.is_pattern_) { -} - -Query::~Query() { -} - -Query Query::String(const std::string& str) { - return Query(str); -} - -Query Query::Double(double num) { - return Query(num); -} - -Query Query::Int(int32_t num) { - return Query(static_cast(num)); -} - -Query Query::Uint(uint32_t num) { - return Query(static_cast(num)); -} - -Query Query::Bool(bool boolean) { - return Query(boolean ? 1.0 : 0.0); -} - -Query Query::Phase(char phase) { - return Query(static_cast(phase)); -} - -Query Query::Pattern(const std::string& pattern) { - Query query(pattern); - query.is_pattern_ = true; - return query; -} - -bool Query::Evaluate(const TraceEvent& event) const { - // First check for values that can convert to bool. - - // double is true if != 0: - double bool_value = 0.0; - bool is_bool = GetAsDouble(event, &bool_value); - if (is_bool) - return (bool_value != 0.0); - - // string is true if it is non-empty: - std::string str_value; - bool is_str = GetAsString(event, &str_value); - if (is_str) - return !str_value.empty(); - - DCHECK_EQ(QUERY_BOOLEAN_OPERATOR, type_) - << "Invalid query: missing boolean expression"; - DCHECK(left_.get()); - DCHECK(right_.get() || is_unary_operator()); - - if (is_comparison_operator()) { - DCHECK(left().is_value() && right().is_value()) - << "Invalid query: comparison operator used between event member and " - "value."; - bool compare_result = false; - if (CompareAsDouble(event, &compare_result)) - return compare_result; - if (CompareAsString(event, &compare_result)) - return compare_result; - return false; - } - // It's a logical operator. - switch (operator_) { - case OP_AND: - return left().Evaluate(event) && right().Evaluate(event); - case OP_OR: - return left().Evaluate(event) || right().Evaluate(event); - case OP_NOT: - return !left().Evaluate(event); - default: - NOTREACHED(); - return false; - } -} - -bool Query::CompareAsDouble(const TraceEvent& event, bool* result) const { - double lhs, rhs; - if (!left().GetAsDouble(event, &lhs) || !right().GetAsDouble(event, &rhs)) - return false; - switch (operator_) { - case OP_EQ: - *result = (lhs == rhs); - return true; - case OP_NE: - *result = (lhs != rhs); - return true; - case OP_LT: - *result = (lhs < rhs); - return true; - case OP_LE: - *result = (lhs <= rhs); - return true; - case OP_GT: - *result = (lhs > rhs); - return true; - case OP_GE: - *result = (lhs >= rhs); - return true; - default: - NOTREACHED(); - return false; - } -} - -bool Query::CompareAsString(const TraceEvent& event, bool* result) const { - std::string lhs, rhs; - if (!left().GetAsString(event, &lhs) || !right().GetAsString(event, &rhs)) - return false; - switch (operator_) { - case OP_EQ: - if (right().is_pattern_) - *result = base::MatchPattern(lhs, rhs); - else if (left().is_pattern_) - *result = base::MatchPattern(rhs, lhs); - else - *result = (lhs == rhs); - return true; - case OP_NE: - if (right().is_pattern_) - *result = !base::MatchPattern(lhs, rhs); - else if (left().is_pattern_) - *result = !base::MatchPattern(rhs, lhs); - else - *result = (lhs != rhs); - return true; - case OP_LT: - *result = (lhs < rhs); - return true; - case OP_LE: - *result = (lhs <= rhs); - return true; - case OP_GT: - *result = (lhs > rhs); - return true; - case OP_GE: - *result = (lhs >= rhs); - return true; - default: - NOTREACHED(); - return false; - } -} - -bool Query::EvaluateArithmeticOperator(const TraceEvent& event, - double* num) const { - DCHECK_EQ(QUERY_ARITHMETIC_OPERATOR, type_); - DCHECK(left_.get()); - DCHECK(right_.get() || is_unary_operator()); - - double lhs = 0, rhs = 0; - if (!left().GetAsDouble(event, &lhs)) - return false; - if (!is_unary_operator() && !right().GetAsDouble(event, &rhs)) - return false; - - switch (operator_) { - case OP_ADD: - *num = lhs + rhs; - return true; - case OP_SUB: - *num = lhs - rhs; - return true; - case OP_MUL: - *num = lhs * rhs; - return true; - case OP_DIV: - *num = lhs / rhs; - return true; - case OP_MOD: - *num = static_cast(static_cast(lhs) % - static_cast(rhs)); - return true; - case OP_NEGATE: - *num = -lhs; - return true; - default: - NOTREACHED(); - return false; - } -} - -bool Query::GetAsDouble(const TraceEvent& event, double* num) const { - switch (type_) { - case QUERY_ARITHMETIC_OPERATOR: - return EvaluateArithmeticOperator(event, num); - case QUERY_EVENT_MEMBER: - return GetMemberValueAsDouble(event, num); - case QUERY_NUMBER: - *num = number_; - return true; - default: - return false; - } -} - -bool Query::GetAsString(const TraceEvent& event, std::string* str) const { - switch (type_) { - case QUERY_EVENT_MEMBER: - return GetMemberValueAsString(event, str); - case QUERY_STRING: - *str = string_; - return true; - default: - return false; - } -} - -const TraceEvent* Query::SelectTargetEvent(const TraceEvent* event, - TraceEventMember member) { - if (member >= OTHER_FIRST_MEMBER && member <= OTHER_LAST_MEMBER) { - return event->other_event; - } else if (member >= PREV_FIRST_MEMBER && member <= PREV_LAST_MEMBER) { - return event->prev_event; - } else { - return event; - } -} - -bool Query::GetMemberValueAsDouble(const TraceEvent& event, - double* num) const { - DCHECK_EQ(QUERY_EVENT_MEMBER, type_); - - // This could be a request for a member of |event| or a member of |event|'s - // associated previous or next event. Store the target event in the_event: - const TraceEvent* the_event = SelectTargetEvent(&event, member_); - - // Request for member of associated event, but there is no associated event. - if (!the_event) - return false; - - switch (member_) { - case EVENT_PID: - case OTHER_PID: - case PREV_PID: - *num = static_cast(the_event->thread.process_id); - return true; - case EVENT_TID: - case OTHER_TID: - case PREV_TID: - *num = static_cast(the_event->thread.thread_id); - return true; - case EVENT_TIME: - case OTHER_TIME: - case PREV_TIME: - *num = the_event->timestamp; - return true; - case EVENT_DURATION: - if (!the_event->has_other_event()) - return false; - *num = the_event->GetAbsTimeToOtherEvent(); - return true; - case EVENT_COMPLETE_DURATION: - if (the_event->phase != TRACE_EVENT_PHASE_COMPLETE) - return false; - *num = the_event->duration; - return true; - case EVENT_PHASE: - case OTHER_PHASE: - case PREV_PHASE: - *num = static_cast(the_event->phase); - return true; - case EVENT_HAS_STRING_ARG: - case OTHER_HAS_STRING_ARG: - case PREV_HAS_STRING_ARG: - *num = (the_event->HasStringArg(string_) ? 1.0 : 0.0); - return true; - case EVENT_HAS_NUMBER_ARG: - case OTHER_HAS_NUMBER_ARG: - case PREV_HAS_NUMBER_ARG: - *num = (the_event->HasNumberArg(string_) ? 1.0 : 0.0); - return true; - case EVENT_ARG: - case OTHER_ARG: - case PREV_ARG: { - // Search for the argument name and return its value if found. - std::map::const_iterator num_i = - the_event->arg_numbers.find(string_); - if (num_i == the_event->arg_numbers.end()) - return false; - *num = num_i->second; - return true; - } - case EVENT_HAS_OTHER: - // return 1.0 (true) if the other event exists - *num = event.other_event ? 1.0 : 0.0; - return true; - case EVENT_HAS_PREV: - *num = event.prev_event ? 1.0 : 0.0; - return true; - default: - return false; - } -} - -bool Query::GetMemberValueAsString(const TraceEvent& event, - std::string* str) const { - DCHECK_EQ(QUERY_EVENT_MEMBER, type_); - - // This could be a request for a member of |event| or a member of |event|'s - // associated previous or next event. Store the target event in the_event: - const TraceEvent* the_event = SelectTargetEvent(&event, member_); - - // Request for member of associated event, but there is no associated event. - if (!the_event) - return false; - - switch (member_) { - case EVENT_CATEGORY: - case OTHER_CATEGORY: - case PREV_CATEGORY: - *str = the_event->category; - return true; - case EVENT_NAME: - case OTHER_NAME: - case PREV_NAME: - *str = the_event->name; - return true; - case EVENT_ID: - case OTHER_ID: - case PREV_ID: - *str = the_event->id; - return true; - case EVENT_ARG: - case OTHER_ARG: - case PREV_ARG: { - // Search for the argument name and return its value if found. - std::map::const_iterator str_i = - the_event->arg_strings.find(string_); - if (str_i == the_event->arg_strings.end()) - return false; - *str = str_i->second; - return true; - } - default: - return false; - } -} - -Query::Query(const std::string& str) - : type_(QUERY_STRING), - operator_(OP_INVALID), - member_(EVENT_INVALID), - number_(0), - string_(str), - is_pattern_(false) { -} - -Query::Query(double num) - : type_(QUERY_NUMBER), - operator_(OP_INVALID), - member_(EVENT_INVALID), - number_(num), - is_pattern_(false) { -} -const Query& Query::left() const { - return left_->query(); -} - -const Query& Query::right() const { - return right_->query(); -} - -Query Query::operator==(const Query& rhs) const { - return Query(*this, rhs, OP_EQ); -} - -Query Query::operator!=(const Query& rhs) const { - return Query(*this, rhs, OP_NE); -} - -Query Query::operator<(const Query& rhs) const { - return Query(*this, rhs, OP_LT); -} - -Query Query::operator<=(const Query& rhs) const { - return Query(*this, rhs, OP_LE); -} - -Query Query::operator>(const Query& rhs) const { - return Query(*this, rhs, OP_GT); -} - -Query Query::operator>=(const Query& rhs) const { - return Query(*this, rhs, OP_GE); -} - -Query Query::operator&&(const Query& rhs) const { - return Query(*this, rhs, OP_AND); -} - -Query Query::operator||(const Query& rhs) const { - return Query(*this, rhs, OP_OR); -} - -Query Query::operator!() const { - return Query(*this, OP_NOT); -} - -Query Query::operator+(const Query& rhs) const { - return Query(*this, rhs, OP_ADD); -} - -Query Query::operator-(const Query& rhs) const { - return Query(*this, rhs, OP_SUB); -} - -Query Query::operator*(const Query& rhs) const { - return Query(*this, rhs, OP_MUL); -} - -Query Query::operator/(const Query& rhs) const { - return Query(*this, rhs, OP_DIV); -} - -Query Query::operator%(const Query& rhs) const { - return Query(*this, rhs, OP_MOD); -} - -Query Query::operator-() const { - return Query(*this, OP_NEGATE); -} - - -Query::Query(const Query& left, const Query& right, Operator binary_op) - : operator_(binary_op), - left_(new QueryNode(left)), - right_(new QueryNode(right)), - member_(EVENT_INVALID), - number_(0) { - type_ = (binary_op < OP_ADD ? - QUERY_BOOLEAN_OPERATOR : QUERY_ARITHMETIC_OPERATOR); -} - -Query::Query(const Query& left, Operator unary_op) - : operator_(unary_op), - left_(new QueryNode(left)), - member_(EVENT_INVALID), - number_(0) { - type_ = (unary_op < OP_ADD ? - QUERY_BOOLEAN_OPERATOR : QUERY_ARITHMETIC_OPERATOR); -} - -namespace { - -// Search |events| for |query| and add matches to |output|. -size_t FindMatchingEvents(const std::vector& events, - const Query& query, - TraceEventVector* output, - bool ignore_metadata_events) { - for (size_t i = 0; i < events.size(); ++i) { - if (ignore_metadata_events && events[i].phase == TRACE_EVENT_PHASE_METADATA) - continue; - if (query.Evaluate(events[i])) - output->push_back(&events[i]); - } - return output->size(); -} - -bool ParseEventsFromJson(const std::string& json, - std::vector* output) { - std::unique_ptr root = base::JSONReader::Read(json); - - base::ListValue* root_list = NULL; - if (!root.get() || !root->GetAsList(&root_list)) - return false; - - for (size_t i = 0; i < root_list->GetSize(); ++i) { - base::Value* item = NULL; - if (root_list->Get(i, &item)) { - TraceEvent event; - if (event.SetFromJSON(item)) - output->push_back(std::move(event)); - else - return false; - } - } - - return true; -} - -} // namespace - -// TraceAnalyzer - -TraceAnalyzer::TraceAnalyzer() - : ignore_metadata_events_(false), - allow_assocation_changes_(true) {} - -TraceAnalyzer::~TraceAnalyzer() { -} - -// static -TraceAnalyzer* TraceAnalyzer::Create(const std::string& json_events) { - std::unique_ptr analyzer(new TraceAnalyzer()); - if (analyzer->SetEvents(json_events)) - return analyzer.release(); - return NULL; -} - -bool TraceAnalyzer::SetEvents(const std::string& json_events) { - raw_events_.clear(); - if (!ParseEventsFromJson(json_events, &raw_events_)) - return false; - std::stable_sort(raw_events_.begin(), raw_events_.end()); - ParseMetadata(); - return true; -} - -void TraceAnalyzer::AssociateBeginEndEvents() { - using trace_analyzer::Query; - - Query begin(Query::EventPhaseIs(TRACE_EVENT_PHASE_BEGIN)); - Query end(Query::EventPhaseIs(TRACE_EVENT_PHASE_END)); - Query match(Query::EventName() == Query::OtherName() && - Query::EventCategory() == Query::OtherCategory() && - Query::EventTid() == Query::OtherTid() && - Query::EventPid() == Query::OtherPid()); - - AssociateEvents(begin, end, match); -} - -void TraceAnalyzer::AssociateAsyncBeginEndEvents(bool match_pid) { - using trace_analyzer::Query; - - Query begin( - Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_BEGIN) || - Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_INTO) || - Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_PAST)); - Query end(Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_END) || - Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_INTO) || - Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_PAST)); - Query match(Query::EventCategory() == Query::OtherCategory() && - Query::EventId() == Query::OtherId()); - - if (match_pid) { - match = match && Query::EventPid() == Query::OtherPid(); - } - - AssociateEvents(begin, end, match); -} - -void TraceAnalyzer::AssociateEvents(const Query& first, - const Query& second, - const Query& match) { - DCHECK(allow_assocation_changes_) - << "AssociateEvents not allowed after FindEvents"; - - // Search for matching begin/end event pairs. When a matching end is found, - // it is associated with the begin event. - std::vector begin_stack; - for (size_t event_index = 0; event_index < raw_events_.size(); - ++event_index) { - - TraceEvent& this_event = raw_events_[event_index]; - - if (second.Evaluate(this_event)) { - // Search stack for matching begin, starting from end. - for (int stack_index = static_cast(begin_stack.size()) - 1; - stack_index >= 0; --stack_index) { - TraceEvent& begin_event = *begin_stack[stack_index]; - - // Temporarily set other to test against the match query. - const TraceEvent* other_backup = begin_event.other_event; - begin_event.other_event = &this_event; - if (match.Evaluate(begin_event)) { - // Found a matching begin/end pair. - // Set the associated previous event - this_event.prev_event = &begin_event; - // Erase the matching begin event index from the stack. - begin_stack.erase(begin_stack.begin() + stack_index); - break; - } - - // Not a match, restore original other and continue. - begin_event.other_event = other_backup; - } - } - // Even if this_event is a |second| event that has matched an earlier - // |first| event, it can still also be a |first| event and be associated - // with a later |second| event. - if (first.Evaluate(this_event)) { - begin_stack.push_back(&this_event); - } - } -} - -void TraceAnalyzer::MergeAssociatedEventArgs() { - for (size_t i = 0; i < raw_events_.size(); ++i) { - // Merge all associated events with the first event. - const TraceEvent* other = raw_events_[i].other_event; - // Avoid looping by keeping set of encountered TraceEvents. - std::set encounters; - encounters.insert(&raw_events_[i]); - while (other && encounters.find(other) == encounters.end()) { - encounters.insert(other); - raw_events_[i].arg_numbers.insert( - other->arg_numbers.begin(), - other->arg_numbers.end()); - raw_events_[i].arg_strings.insert( - other->arg_strings.begin(), - other->arg_strings.end()); - other = other->other_event; - } - } -} - -size_t TraceAnalyzer::FindEvents(const Query& query, TraceEventVector* output) { - allow_assocation_changes_ = false; - output->clear(); - return FindMatchingEvents( - raw_events_, query, output, ignore_metadata_events_); -} - -const TraceEvent* TraceAnalyzer::FindFirstOf(const Query& query) { - TraceEventVector output; - if (FindEvents(query, &output) > 0) - return output.front(); - return NULL; -} - -const TraceEvent* TraceAnalyzer::FindLastOf(const Query& query) { - TraceEventVector output; - if (FindEvents(query, &output) > 0) - return output.back(); - return NULL; -} - -const std::string& TraceAnalyzer::GetThreadName( - const TraceEvent::ProcessThreadID& thread) { - // If thread is not found, just add and return empty string. - return thread_names_[thread]; -} - -void TraceAnalyzer::ParseMetadata() { - for (size_t i = 0; i < raw_events_.size(); ++i) { - TraceEvent& this_event = raw_events_[i]; - // Check for thread name metadata. - if (this_event.phase != TRACE_EVENT_PHASE_METADATA || - this_event.name != "thread_name") - continue; - std::map::const_iterator string_it = - this_event.arg_strings.find("name"); - if (string_it != this_event.arg_strings.end()) - thread_names_[this_event.thread] = string_it->second; - } -} - -// TraceEventVector utility functions. - -bool GetRateStats(const TraceEventVector& events, - RateStats* stats, - const RateStatsOptions* options) { - DCHECK(stats); - // Need at least 3 events to calculate rate stats. - const size_t kMinEvents = 3; - if (events.size() < kMinEvents) { - LOG(ERROR) << "Not enough events: " << events.size(); - return false; - } - - std::vector deltas; - size_t num_deltas = events.size() - 1; - for (size_t i = 0; i < num_deltas; ++i) { - double delta = events.at(i + 1)->timestamp - events.at(i)->timestamp; - if (delta < 0.0) { - LOG(ERROR) << "Events are out of order"; - return false; - } - deltas.push_back(delta); - } - - std::sort(deltas.begin(), deltas.end()); - - if (options) { - if (options->trim_min + options->trim_max > events.size() - kMinEvents) { - LOG(ERROR) << "Attempt to trim too many events"; - return false; - } - deltas.erase(deltas.begin(), deltas.begin() + options->trim_min); - deltas.erase(deltas.end() - options->trim_max, deltas.end()); - } - - num_deltas = deltas.size(); - double delta_sum = 0.0; - for (size_t i = 0; i < num_deltas; ++i) - delta_sum += deltas[i]; - - stats->min_us = *std::min_element(deltas.begin(), deltas.end()); - stats->max_us = *std::max_element(deltas.begin(), deltas.end()); - stats->mean_us = delta_sum / static_cast(num_deltas); - - double sum_mean_offsets_squared = 0.0; - for (size_t i = 0; i < num_deltas; ++i) { - double offset = fabs(deltas[i] - stats->mean_us); - sum_mean_offsets_squared += offset * offset; - } - stats->standard_deviation_us = - sqrt(sum_mean_offsets_squared / static_cast(num_deltas - 1)); - - return true; -} - -bool FindFirstOf(const TraceEventVector& events, - const Query& query, - size_t position, - size_t* return_index) { - DCHECK(return_index); - for (size_t i = position; i < events.size(); ++i) { - if (query.Evaluate(*events[i])) { - *return_index = i; - return true; - } - } - return false; -} - -bool FindLastOf(const TraceEventVector& events, - const Query& query, - size_t position, - size_t* return_index) { - DCHECK(return_index); - for (size_t i = std::min(position + 1, events.size()); i != 0; --i) { - if (query.Evaluate(*events[i - 1])) { - *return_index = i - 1; - return true; - } - } - return false; -} - -bool FindClosest(const TraceEventVector& events, - const Query& query, - size_t position, - size_t* return_closest, - size_t* return_second_closest) { - DCHECK(return_closest); - if (events.empty() || position >= events.size()) - return false; - size_t closest = events.size(); - size_t second_closest = events.size(); - for (size_t i = 0; i < events.size(); ++i) { - if (!query.Evaluate(*events.at(i))) - continue; - if (closest == events.size()) { - closest = i; - continue; - } - if (fabs(events.at(i)->timestamp - events.at(position)->timestamp) < - fabs(events.at(closest)->timestamp - events.at(position)->timestamp)) { - second_closest = closest; - closest = i; - } else if (second_closest == events.size()) { - second_closest = i; - } - } - - if (closest < events.size() && - (!return_second_closest || second_closest < events.size())) { - *return_closest = closest; - if (return_second_closest) - *return_second_closest = second_closest; - return true; - } - - return false; -} - -size_t CountMatches(const TraceEventVector& events, - const Query& query, - size_t begin_position, - size_t end_position) { - if (begin_position >= events.size()) - return 0u; - end_position = (end_position < events.size()) ? end_position : events.size(); - size_t count = 0u; - for (size_t i = begin_position; i < end_position; ++i) { - if (query.Evaluate(*events.at(i))) - ++count; - } - return count; -} - -} // namespace trace_analyzer diff --git a/base/test/trace_event_analyzer.h b/base/test/trace_event_analyzer.h deleted file mode 100644 index e43a52564401140dadced9f3a329079a36e444c6..0000000000000000000000000000000000000000 --- a/base/test/trace_event_analyzer.h +++ /dev/null @@ -1,810 +0,0 @@ -// Copyright (c) 2012 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. - -// Use trace_analyzer::Query and trace_analyzer::TraceAnalyzer to search for -// specific trace events that were generated by the trace_event.h API. -// -// Basic procedure: -// - Get trace events JSON string from base::trace_event::TraceLog. -// - Create TraceAnalyzer with JSON string. -// - Call TraceAnalyzer::AssociateBeginEndEvents (optional). -// - Call TraceAnalyzer::AssociateEvents (zero or more times). -// - Call TraceAnalyzer::FindEvents with queries to find specific events. -// -// A Query is a boolean expression tree that evaluates to true or false for a -// given trace event. Queries can be combined into a tree using boolean, -// arithmetic and comparison operators that refer to data of an individual trace -// event. -// -// The events are returned as trace_analyzer::TraceEvent objects. -// TraceEvent contains a single trace event's data, as well as a pointer to -// a related trace event. The related trace event is typically the matching end -// of a begin event or the matching begin of an end event. -// -// The following examples use this basic setup code to construct TraceAnalyzer -// with the json trace string retrieved from TraceLog and construct an event -// vector for retrieving events: -// -// TraceAnalyzer analyzer(json_events); -// TraceEventVector events; -// -// EXAMPLE 1: Find events named "my_event". -// -// analyzer.FindEvents(Query(EVENT_NAME) == "my_event", &events); -// -// EXAMPLE 2: Find begin events named "my_event" with duration > 1 second. -// -// Query q = (Query(EVENT_NAME) == Query::String("my_event") && -// Query(EVENT_PHASE) == Query::Phase(TRACE_EVENT_PHASE_BEGIN) && -// Query(EVENT_DURATION) > Query::Double(1000000.0)); -// analyzer.FindEvents(q, &events); -// -// EXAMPLE 3: Associating event pairs across threads. -// -// If the test needs to analyze something that starts and ends on different -// threads, the test needs to use INSTANT events. The typical procedure is to -// specify the same unique ID as a TRACE_EVENT argument on both the start and -// finish INSTANT events. Then use the following procedure to associate those -// events. -// -// Step 1: instrument code with custom begin/end trace events. -// [Thread 1 tracing code] -// TRACE_EVENT_INSTANT1("test_latency", "timing1_begin", "id", 3); -// [Thread 2 tracing code] -// TRACE_EVENT_INSTANT1("test_latency", "timing1_end", "id", 3); -// -// Step 2: associate these custom begin/end pairs. -// Query begin(Query(EVENT_NAME) == Query::String("timing1_begin")); -// Query end(Query(EVENT_NAME) == Query::String("timing1_end")); -// Query match(Query(EVENT_ARG, "id") == Query(OTHER_ARG, "id")); -// analyzer.AssociateEvents(begin, end, match); -// -// Step 3: search for "timing1_begin" events with existing other event. -// Query q = (Query(EVENT_NAME) == Query::String("timing1_begin") && -// Query(EVENT_HAS_OTHER)); -// analyzer.FindEvents(q, &events); -// -// Step 4: analyze events, such as checking durations. -// for (size_t i = 0; i < events.size(); ++i) { -// double duration; -// EXPECT_TRUE(events[i].GetAbsTimeToOtherEvent(&duration)); -// EXPECT_LT(duration, 1000000.0/60.0); // expect less than 1/60 second. -// } - - -#ifndef BASE_TEST_TRACE_EVENT_ANALYZER_H_ -#define BASE_TEST_TRACE_EVENT_ANALYZER_H_ - -#include -#include - -#include - -#include "base/macros.h" -#include "base/memory/ref_counted.h" -#include "base/trace_event/trace_event.h" - -namespace base { -class Value; -} - -namespace trace_analyzer { -class QueryNode; - -// trace_analyzer::TraceEvent is a more convenient form of the -// base::trace_event::TraceEvent class to make tracing-based tests easier to -// write. -struct TraceEvent { - // ProcessThreadID contains a Process ID and Thread ID. - struct ProcessThreadID { - ProcessThreadID() : process_id(0), thread_id(0) {} - ProcessThreadID(int process_id, int thread_id) - : process_id(process_id), thread_id(thread_id) {} - bool operator< (const ProcessThreadID& rhs) const { - if (process_id != rhs.process_id) - return process_id < rhs.process_id; - return thread_id < rhs.thread_id; - } - int process_id; - int thread_id; - }; - - TraceEvent(); - TraceEvent(TraceEvent&& other); - ~TraceEvent(); - - bool SetFromJSON(const base::Value* event_value) WARN_UNUSED_RESULT; - - bool operator< (const TraceEvent& rhs) const { - return timestamp < rhs.timestamp; - } - - TraceEvent& operator=(TraceEvent&& rhs); - - bool has_other_event() const { return other_event; } - - // Returns absolute duration in microseconds between this event and other - // event. Must have already verified that other_event exists by - // Query(EVENT_HAS_OTHER) or by calling has_other_event(). - double GetAbsTimeToOtherEvent() const; - - // Return the argument value if it exists and it is a string. - bool GetArgAsString(const std::string& name, std::string* arg) const; - // Return the argument value if it exists and it is a number. - bool GetArgAsNumber(const std::string& name, double* arg) const; - // Return the argument value if it exists. - bool GetArgAsValue(const std::string& name, - std::unique_ptr* arg) const; - - // Check if argument exists and is string. - bool HasStringArg(const std::string& name) const; - // Check if argument exists and is number (double, int or bool). - bool HasNumberArg(const std::string& name) const; - // Check if argument exists. - bool HasArg(const std::string& name) const; - - // Get known existing arguments as specific types. - // Useful when you have already queried the argument with - // Query(HAS_NUMBER_ARG) or Query(HAS_STRING_ARG). - std::string GetKnownArgAsString(const std::string& name) const; - double GetKnownArgAsDouble(const std::string& name) const; - int GetKnownArgAsInt(const std::string& name) const; - bool GetKnownArgAsBool(const std::string& name) const; - std::unique_ptr GetKnownArgAsValue( - const std::string& name) const; - - // Process ID and Thread ID. - ProcessThreadID thread; - - // Time since epoch in microseconds. - // Stored as double to match its JSON representation. - double timestamp; - double duration; - char phase; - std::string category; - std::string name; - std::string id; - - // All numbers and bool values from TraceEvent args are cast to double. - // bool becomes 1.0 (true) or 0.0 (false). - std::map arg_numbers; - std::map arg_strings; - std::map> arg_values; - - // The other event associated with this event (or NULL). - const TraceEvent* other_event; - - // A back-link for |other_event|. That is, if other_event is not null, then - // |event->other_event->prev_event == event| is always true. - const TraceEvent* prev_event; -}; - -typedef std::vector TraceEventVector; - -class Query { - public: - Query(const Query& query); - - ~Query(); - - //////////////////////////////////////////////////////////////// - // Query literal values - - // Compare with the given string. - static Query String(const std::string& str); - - // Compare with the given number. - static Query Double(double num); - static Query Int(int32_t num); - static Query Uint(uint32_t num); - - // Compare with the given bool. - static Query Bool(bool boolean); - - // Compare with the given phase. - static Query Phase(char phase); - - // Compare with the given string pattern. Only works with == and != operators. - // Example: Query(EVENT_NAME) == Query::Pattern("MyEvent*") - static Query Pattern(const std::string& pattern); - - //////////////////////////////////////////////////////////////// - // Query event members - - static Query EventPid() { return Query(EVENT_PID); } - - static Query EventTid() { return Query(EVENT_TID); } - - // Return the timestamp of the event in microseconds since epoch. - static Query EventTime() { return Query(EVENT_TIME); } - - // Return the absolute time between event and other event in microseconds. - // Only works if Query::EventHasOther() == true. - static Query EventDuration() { return Query(EVENT_DURATION); } - - // Return the duration of a COMPLETE event. - static Query EventCompleteDuration() { - return Query(EVENT_COMPLETE_DURATION); - } - - static Query EventPhase() { return Query(EVENT_PHASE); } - - static Query EventCategory() { return Query(EVENT_CATEGORY); } - - static Query EventName() { return Query(EVENT_NAME); } - - static Query EventId() { return Query(EVENT_ID); } - - static Query EventPidIs(int process_id) { - return Query(EVENT_PID) == Query::Int(process_id); - } - - static Query EventTidIs(int thread_id) { - return Query(EVENT_TID) == Query::Int(thread_id); - } - - static Query EventThreadIs(const TraceEvent::ProcessThreadID& thread) { - return EventPidIs(thread.process_id) && EventTidIs(thread.thread_id); - } - - static Query EventTimeIs(double timestamp) { - return Query(EVENT_TIME) == Query::Double(timestamp); - } - - static Query EventDurationIs(double duration) { - return Query(EVENT_DURATION) == Query::Double(duration); - } - - static Query EventPhaseIs(char phase) { - return Query(EVENT_PHASE) == Query::Phase(phase); - } - - static Query EventCategoryIs(const std::string& category) { - return Query(EVENT_CATEGORY) == Query::String(category); - } - - static Query EventNameIs(const std::string& name) { - return Query(EVENT_NAME) == Query::String(name); - } - - static Query EventIdIs(const std::string& id) { - return Query(EVENT_ID) == Query::String(id); - } - - // Evaluates to true if arg exists and is a string. - static Query EventHasStringArg(const std::string& arg_name) { - return Query(EVENT_HAS_STRING_ARG, arg_name); - } - - // Evaluates to true if arg exists and is a number. - // Number arguments include types double, int and bool. - static Query EventHasNumberArg(const std::string& arg_name) { - return Query(EVENT_HAS_NUMBER_ARG, arg_name); - } - - // Evaluates to arg value (string or number). - static Query EventArg(const std::string& arg_name) { - return Query(EVENT_ARG, arg_name); - } - - // Return true if associated event exists. - static Query EventHasOther() { return Query(EVENT_HAS_OTHER); } - - // Access the associated other_event's members: - - static Query OtherPid() { return Query(OTHER_PID); } - - static Query OtherTid() { return Query(OTHER_TID); } - - static Query OtherTime() { return Query(OTHER_TIME); } - - static Query OtherPhase() { return Query(OTHER_PHASE); } - - static Query OtherCategory() { return Query(OTHER_CATEGORY); } - - static Query OtherName() { return Query(OTHER_NAME); } - - static Query OtherId() { return Query(OTHER_ID); } - - static Query OtherPidIs(int process_id) { - return Query(OTHER_PID) == Query::Int(process_id); - } - - static Query OtherTidIs(int thread_id) { - return Query(OTHER_TID) == Query::Int(thread_id); - } - - static Query OtherThreadIs(const TraceEvent::ProcessThreadID& thread) { - return OtherPidIs(thread.process_id) && OtherTidIs(thread.thread_id); - } - - static Query OtherTimeIs(double timestamp) { - return Query(OTHER_TIME) == Query::Double(timestamp); - } - - static Query OtherPhaseIs(char phase) { - return Query(OTHER_PHASE) == Query::Phase(phase); - } - - static Query OtherCategoryIs(const std::string& category) { - return Query(OTHER_CATEGORY) == Query::String(category); - } - - static Query OtherNameIs(const std::string& name) { - return Query(OTHER_NAME) == Query::String(name); - } - - static Query OtherIdIs(const std::string& id) { - return Query(OTHER_ID) == Query::String(id); - } - - // Evaluates to true if arg exists and is a string. - static Query OtherHasStringArg(const std::string& arg_name) { - return Query(OTHER_HAS_STRING_ARG, arg_name); - } - - // Evaluates to true if arg exists and is a number. - // Number arguments include types double, int and bool. - static Query OtherHasNumberArg(const std::string& arg_name) { - return Query(OTHER_HAS_NUMBER_ARG, arg_name); - } - - // Evaluates to arg value (string or number). - static Query OtherArg(const std::string& arg_name) { - return Query(OTHER_ARG, arg_name); - } - - // Access the associated prev_event's members: - - static Query PrevPid() { return Query(PREV_PID); } - - static Query PrevTid() { return Query(PREV_TID); } - - static Query PrevTime() { return Query(PREV_TIME); } - - static Query PrevPhase() { return Query(PREV_PHASE); } - - static Query PrevCategory() { return Query(PREV_CATEGORY); } - - static Query PrevName() { return Query(PREV_NAME); } - - static Query PrevId() { return Query(PREV_ID); } - - static Query PrevPidIs(int process_id) { - return Query(PREV_PID) == Query::Int(process_id); - } - - static Query PrevTidIs(int thread_id) { - return Query(PREV_TID) == Query::Int(thread_id); - } - - static Query PrevThreadIs(const TraceEvent::ProcessThreadID& thread) { - return PrevPidIs(thread.process_id) && PrevTidIs(thread.thread_id); - } - - static Query PrevTimeIs(double timestamp) { - return Query(PREV_TIME) == Query::Double(timestamp); - } - - static Query PrevPhaseIs(char phase) { - return Query(PREV_PHASE) == Query::Phase(phase); - } - - static Query PrevCategoryIs(const std::string& category) { - return Query(PREV_CATEGORY) == Query::String(category); - } - - static Query PrevNameIs(const std::string& name) { - return Query(PREV_NAME) == Query::String(name); - } - - static Query PrevIdIs(const std::string& id) { - return Query(PREV_ID) == Query::String(id); - } - - // Evaluates to true if arg exists and is a string. - static Query PrevHasStringArg(const std::string& arg_name) { - return Query(PREV_HAS_STRING_ARG, arg_name); - } - - // Evaluates to true if arg exists and is a number. - // Number arguments include types double, int and bool. - static Query PrevHasNumberArg(const std::string& arg_name) { - return Query(PREV_HAS_NUMBER_ARG, arg_name); - } - - // Evaluates to arg value (string or number). - static Query PrevArg(const std::string& arg_name) { - return Query(PREV_ARG, arg_name); - } - - //////////////////////////////////////////////////////////////// - // Common queries: - - // Find BEGIN events that have a corresponding END event. - static Query MatchBeginWithEnd() { - return (Query(EVENT_PHASE) == Query::Phase(TRACE_EVENT_PHASE_BEGIN)) && - Query(EVENT_HAS_OTHER); - } - - // Find COMPLETE events. - static Query MatchComplete() { - return (Query(EVENT_PHASE) == Query::Phase(TRACE_EVENT_PHASE_COMPLETE)); - } - - // Find ASYNC_BEGIN events that have a corresponding ASYNC_END event. - static Query MatchAsyncBeginWithNext() { - return (Query(EVENT_PHASE) == - Query::Phase(TRACE_EVENT_PHASE_ASYNC_BEGIN)) && - Query(EVENT_HAS_OTHER); - } - - // Find BEGIN events of given |name| which also have associated END events. - static Query MatchBeginName(const std::string& name) { - return (Query(EVENT_NAME) == Query(name)) && MatchBeginWithEnd(); - } - - // Find COMPLETE events of given |name|. - static Query MatchCompleteName(const std::string& name) { - return (Query(EVENT_NAME) == Query(name)) && MatchComplete(); - } - - // Match given Process ID and Thread ID. - static Query MatchThread(const TraceEvent::ProcessThreadID& thread) { - return (Query(EVENT_PID) == Query::Int(thread.process_id)) && - (Query(EVENT_TID) == Query::Int(thread.thread_id)); - } - - // Match event pair that spans multiple threads. - static Query MatchCrossThread() { - return (Query(EVENT_PID) != Query(OTHER_PID)) || - (Query(EVENT_TID) != Query(OTHER_TID)); - } - - //////////////////////////////////////////////////////////////// - // Operators: - - // Boolean operators: - Query operator==(const Query& rhs) const; - Query operator!=(const Query& rhs) const; - Query operator< (const Query& rhs) const; - Query operator<=(const Query& rhs) const; - Query operator> (const Query& rhs) const; - Query operator>=(const Query& rhs) const; - Query operator&&(const Query& rhs) const; - Query operator||(const Query& rhs) const; - Query operator!() const; - - // Arithmetic operators: - // Following operators are applied to double arguments: - Query operator+(const Query& rhs) const; - Query operator-(const Query& rhs) const; - Query operator*(const Query& rhs) const; - Query operator/(const Query& rhs) const; - Query operator-() const; - // Mod operates on int64_t args (doubles are casted to int64_t beforehand): - Query operator%(const Query& rhs) const; - - // Return true if the given event matches this query tree. - // This is a recursive method that walks the query tree. - bool Evaluate(const TraceEvent& event) const; - - private: - enum TraceEventMember { - EVENT_INVALID, - EVENT_PID, - EVENT_TID, - EVENT_TIME, - EVENT_DURATION, - EVENT_COMPLETE_DURATION, - EVENT_PHASE, - EVENT_CATEGORY, - EVENT_NAME, - EVENT_ID, - EVENT_HAS_STRING_ARG, - EVENT_HAS_NUMBER_ARG, - EVENT_ARG, - EVENT_HAS_OTHER, - EVENT_HAS_PREV, - - OTHER_PID, - OTHER_TID, - OTHER_TIME, - OTHER_PHASE, - OTHER_CATEGORY, - OTHER_NAME, - OTHER_ID, - OTHER_HAS_STRING_ARG, - OTHER_HAS_NUMBER_ARG, - OTHER_ARG, - - PREV_PID, - PREV_TID, - PREV_TIME, - PREV_PHASE, - PREV_CATEGORY, - PREV_NAME, - PREV_ID, - PREV_HAS_STRING_ARG, - PREV_HAS_NUMBER_ARG, - PREV_ARG, - - OTHER_FIRST_MEMBER = OTHER_PID, - OTHER_LAST_MEMBER = OTHER_ARG, - - PREV_FIRST_MEMBER = PREV_PID, - PREV_LAST_MEMBER = PREV_ARG, - }; - - enum Operator { - OP_INVALID, - // Boolean operators: - OP_EQ, - OP_NE, - OP_LT, - OP_LE, - OP_GT, - OP_GE, - OP_AND, - OP_OR, - OP_NOT, - // Arithmetic operators: - OP_ADD, - OP_SUB, - OP_MUL, - OP_DIV, - OP_MOD, - OP_NEGATE - }; - - enum QueryType { - QUERY_BOOLEAN_OPERATOR, - QUERY_ARITHMETIC_OPERATOR, - QUERY_EVENT_MEMBER, - QUERY_NUMBER, - QUERY_STRING - }; - - // Compare with the given member. - explicit Query(TraceEventMember member); - - // Compare with the given member argument value. - Query(TraceEventMember member, const std::string& arg_name); - - // Compare with the given string. - explicit Query(const std::string& str); - - // Compare with the given number. - explicit Query(double num); - - // Construct a boolean Query that returns (left right). - Query(const Query& left, const Query& right, Operator binary_op); - - // Construct a boolean Query that returns ( left). - Query(const Query& left, Operator unary_op); - - // Try to compare left_ against right_ based on operator_. - // If either left or right does not convert to double, false is returned. - // Otherwise, true is returned and |result| is set to the comparison result. - bool CompareAsDouble(const TraceEvent& event, bool* result) const; - - // Try to compare left_ against right_ based on operator_. - // If either left or right does not convert to string, false is returned. - // Otherwise, true is returned and |result| is set to the comparison result. - bool CompareAsString(const TraceEvent& event, bool* result) const; - - // Attempt to convert this Query to a double. On success, true is returned - // and the double value is stored in |num|. - bool GetAsDouble(const TraceEvent& event, double* num) const; - - // Attempt to convert this Query to a string. On success, true is returned - // and the string value is stored in |str|. - bool GetAsString(const TraceEvent& event, std::string* str) const; - - // Evaluate this Query as an arithmetic operator on left_ and right_. - bool EvaluateArithmeticOperator(const TraceEvent& event, - double* num) const; - - // For QUERY_EVENT_MEMBER Query: attempt to get the double value of the Query. - bool GetMemberValueAsDouble(const TraceEvent& event, double* num) const; - - // For QUERY_EVENT_MEMBER Query: attempt to get the string value of the Query. - bool GetMemberValueAsString(const TraceEvent& event, std::string* num) const; - - // Does this Query represent a value? - bool is_value() const { return type_ != QUERY_BOOLEAN_OPERATOR; } - - bool is_unary_operator() const { - return operator_ == OP_NOT || operator_ == OP_NEGATE; - } - - bool is_comparison_operator() const { - return operator_ != OP_INVALID && operator_ < OP_AND; - } - - static const TraceEvent* SelectTargetEvent(const TraceEvent* ev, - TraceEventMember member); - - const Query& left() const; - const Query& right() const; - - QueryType type_; - Operator operator_; - scoped_refptr left_; - scoped_refptr right_; - TraceEventMember member_; - double number_; - std::string string_; - bool is_pattern_; -}; - -// Implementation detail: -// QueryNode allows Query to store a ref-counted query tree. -class QueryNode : public base::RefCounted { - public: - explicit QueryNode(const Query& query); - const Query& query() const { return query_; } - - private: - friend class base::RefCounted; - ~QueryNode(); - - Query query_; -}; - -// TraceAnalyzer helps tests search for trace events. -class TraceAnalyzer { - public: - ~TraceAnalyzer(); - - // Use trace events from JSON string generated by tracing API. - // Returns non-NULL if the JSON is successfully parsed. - static TraceAnalyzer* Create(const std::string& json_events) - WARN_UNUSED_RESULT; - - void SetIgnoreMetadataEvents(bool ignore) { - ignore_metadata_events_ = ignore; - } - - // Associate BEGIN and END events with each other. This allows Query(OTHER_*) - // to access the associated event and enables Query(EVENT_DURATION). - // An end event will match the most recent begin event with the same name, - // category, process ID and thread ID. This matches what is shown in - // about:tracing. After association, the BEGIN event will point to the - // matching END event, but the END event will not point to the BEGIN event. - void AssociateBeginEndEvents(); - - // Associate ASYNC_BEGIN, ASYNC_STEP and ASYNC_END events with each other. - // An ASYNC_END event will match the most recent ASYNC_BEGIN or ASYNC_STEP - // event with the same name, category, and ID. This creates a singly linked - // list of ASYNC_BEGIN->ASYNC_STEP...->ASYNC_END. - // |match_pid| - If true, will only match async events which are running - // under the same process ID, otherwise will allow linking - // async events from different processes. - void AssociateAsyncBeginEndEvents(bool match_pid = true); - - // AssociateEvents can be used to customize event associations by setting the - // other_event member of TraceEvent. This should be used to associate two - // INSTANT events. - // - // The assumptions are: - // - |first| events occur before |second| events. - // - the closest matching |second| event is the correct match. - // - // |first| - Eligible |first| events match this query. - // |second| - Eligible |second| events match this query. - // |match| - This query is run on the |first| event. The OTHER_* EventMember - // queries will point to an eligible |second| event. The query - // should evaluate to true if the |first|/|second| pair is a match. - // - // When a match is found, the pair will be associated by having the first - // event's other_event member point to the other. AssociateEvents does not - // clear previous associations, so it is possible to associate multiple pairs - // of events by calling AssociateEvents more than once with different queries. - // - // NOTE: AssociateEvents will overwrite existing other_event associations if - // the queries pass for events that already had a previous association. - // - // After calling any Find* method, it is not allowed to call AssociateEvents - // again. - void AssociateEvents(const Query& first, - const Query& second, - const Query& match); - - // For each event, copy its arguments to the other_event argument map. If - // argument name already exists, it will not be overwritten. - void MergeAssociatedEventArgs(); - - // Find all events that match query and replace output vector. - size_t FindEvents(const Query& query, TraceEventVector* output); - - // Find first event that matches query or NULL if not found. - const TraceEvent* FindFirstOf(const Query& query); - - // Find last event that matches query or NULL if not found. - const TraceEvent* FindLastOf(const Query& query); - - const std::string& GetThreadName(const TraceEvent::ProcessThreadID& thread); - - private: - TraceAnalyzer(); - - bool SetEvents(const std::string& json_events) WARN_UNUSED_RESULT; - - // Read metadata (thread names, etc) from events. - void ParseMetadata(); - - std::map thread_names_; - std::vector raw_events_; - bool ignore_metadata_events_; - bool allow_assocation_changes_; - - DISALLOW_COPY_AND_ASSIGN(TraceAnalyzer); -}; - -// Utility functions for TraceEventVector. - -struct RateStats { - double min_us; - double max_us; - double mean_us; - double standard_deviation_us; -}; - -struct RateStatsOptions { - RateStatsOptions() : trim_min(0u), trim_max(0u) {} - // After the times between events are sorted, the number of specified elements - // will be trimmed before calculating the RateStats. This is useful in cases - // where extreme outliers are tolerable and should not skew the overall - // average. - size_t trim_min; // Trim this many minimum times. - size_t trim_max; // Trim this many maximum times. -}; - -// Calculate min/max/mean and standard deviation from the times between -// adjacent events. -bool GetRateStats(const TraceEventVector& events, - RateStats* stats, - const RateStatsOptions* options); - -// Starting from |position|, find the first event that matches |query|. -// Returns true if found, false otherwise. -bool FindFirstOf(const TraceEventVector& events, - const Query& query, - size_t position, - size_t* return_index); - -// Starting from |position|, find the last event that matches |query|. -// Returns true if found, false otherwise. -bool FindLastOf(const TraceEventVector& events, - const Query& query, - size_t position, - size_t* return_index); - -// Find the closest events to |position| in time that match |query|. -// return_second_closest may be NULL. Closeness is determined by comparing -// with the event timestamp. -// Returns true if found, false otherwise. If both return parameters are -// requested, both must be found for a successful result. -bool FindClosest(const TraceEventVector& events, - const Query& query, - size_t position, - size_t* return_closest, - size_t* return_second_closest); - -// Count matches, inclusive of |begin_position|, exclusive of |end_position|. -size_t CountMatches(const TraceEventVector& events, - const Query& query, - size_t begin_position, - size_t end_position); - -// Count all matches. -static inline size_t CountMatches(const TraceEventVector& events, - const Query& query) { - return CountMatches(events, query, 0u, events.size()); -} - -} // namespace trace_analyzer - -#endif // BASE_TEST_TRACE_EVENT_ANALYZER_H_ diff --git a/base/test/trace_event_analyzer_unittest.cc b/base/test/trace_event_analyzer_unittest.cc deleted file mode 100644 index ce7bce22a0ac206b0450c12dd89e262b35f95f86..0000000000000000000000000000000000000000 --- a/base/test/trace_event_analyzer_unittest.cc +++ /dev/null @@ -1,961 +0,0 @@ -// Copyright (c) 2012 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. - -#include "base/test/trace_event_analyzer.h" - -#include -#include - -#include "base/bind.h" -#include "base/memory/ptr_util.h" -#include "base/memory/ref_counted_memory.h" -#include "base/synchronization/waitable_event.h" -#include "base/threading/platform_thread.h" -#include "base/trace_event/trace_buffer.h" -#include "base/trace_event/trace_event_argument.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace trace_analyzer { - -namespace { - -class TraceEventAnalyzerTest : public testing::Test { - public: - void ManualSetUp(); - void OnTraceDataCollected( - base::WaitableEvent* flush_complete_event, - const scoped_refptr& json_events_str, - bool has_more_events); - void BeginTracing(); - void EndTracing(); - - base::trace_event::TraceResultBuffer::SimpleOutput output_; - base::trace_event::TraceResultBuffer buffer_; -}; - -void TraceEventAnalyzerTest::ManualSetUp() { - ASSERT_TRUE(base::trace_event::TraceLog::GetInstance()); - buffer_.SetOutputCallback(output_.GetCallback()); - output_.json_output.clear(); -} - -void TraceEventAnalyzerTest::OnTraceDataCollected( - base::WaitableEvent* flush_complete_event, - const scoped_refptr& json_events_str, - bool has_more_events) { - buffer_.AddFragment(json_events_str->data()); - if (!has_more_events) - flush_complete_event->Signal(); -} - -void TraceEventAnalyzerTest::BeginTracing() { - output_.json_output.clear(); - buffer_.Start(); - base::trace_event::TraceLog::GetInstance()->SetEnabled( - base::trace_event::TraceConfig("*", ""), - base::trace_event::TraceLog::RECORDING_MODE); -} - -void TraceEventAnalyzerTest::EndTracing() { - base::trace_event::TraceLog::GetInstance()->SetDisabled(); - base::WaitableEvent flush_complete_event( - base::WaitableEvent::ResetPolicy::AUTOMATIC, - base::WaitableEvent::InitialState::NOT_SIGNALED); - base::trace_event::TraceLog::GetInstance()->Flush( - base::Bind(&TraceEventAnalyzerTest::OnTraceDataCollected, - base::Unretained(this), - base::Unretained(&flush_complete_event))); - flush_complete_event.Wait(); - buffer_.Finish(); -} - -} // namespace - -TEST_F(TraceEventAnalyzerTest, NoEvents) { - ManualSetUp(); - - // Create an empty JSON event string: - buffer_.Start(); - buffer_.Finish(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer.get()); - - // Search for all events and verify that nothing is returned. - TraceEventVector found; - analyzer->FindEvents(Query::Bool(true), &found); - EXPECT_EQ(0u, found.size()); -} - -TEST_F(TraceEventAnalyzerTest, TraceEvent) { - ManualSetUp(); - - int int_num = 2; - double double_num = 3.5; - const char str[] = "the string"; - - TraceEvent event; - event.arg_numbers["false"] = 0.0; - event.arg_numbers["true"] = 1.0; - event.arg_numbers["int"] = static_cast(int_num); - event.arg_numbers["double"] = double_num; - event.arg_strings["string"] = str; - event.arg_values["dict"] = WrapUnique(new base::DictionaryValue()); - - ASSERT_TRUE(event.HasNumberArg("false")); - ASSERT_TRUE(event.HasNumberArg("true")); - ASSERT_TRUE(event.HasNumberArg("int")); - ASSERT_TRUE(event.HasNumberArg("double")); - ASSERT_TRUE(event.HasStringArg("string")); - ASSERT_FALSE(event.HasNumberArg("notfound")); - ASSERT_FALSE(event.HasStringArg("notfound")); - ASSERT_TRUE(event.HasArg("dict")); - ASSERT_FALSE(event.HasArg("notfound")); - - EXPECT_FALSE(event.GetKnownArgAsBool("false")); - EXPECT_TRUE(event.GetKnownArgAsBool("true")); - EXPECT_EQ(int_num, event.GetKnownArgAsInt("int")); - EXPECT_EQ(double_num, event.GetKnownArgAsDouble("double")); - EXPECT_STREQ(str, event.GetKnownArgAsString("string").c_str()); - - std::unique_ptr arg; - EXPECT_TRUE(event.GetArgAsValue("dict", &arg)); - EXPECT_EQ(base::Value::Type::DICTIONARY, arg->GetType()); -} - -TEST_F(TraceEventAnalyzerTest, QueryEventMember) { - ManualSetUp(); - - TraceEvent event; - event.thread.process_id = 3; - event.thread.thread_id = 4; - event.timestamp = 1.5; - event.phase = TRACE_EVENT_PHASE_BEGIN; - event.category = "category"; - event.name = "name"; - event.id = "1"; - event.arg_numbers["num"] = 7.0; - event.arg_strings["str"] = "the string"; - - // Other event with all different members: - TraceEvent other; - other.thread.process_id = 5; - other.thread.thread_id = 6; - other.timestamp = 2.5; - other.phase = TRACE_EVENT_PHASE_END; - other.category = "category2"; - other.name = "name2"; - other.id = "2"; - other.arg_numbers["num2"] = 8.0; - other.arg_strings["str2"] = "the string 2"; - - event.other_event = &other; - ASSERT_TRUE(event.has_other_event()); - double duration = event.GetAbsTimeToOtherEvent(); - - Query event_pid = Query::EventPidIs(event.thread.process_id); - Query event_tid = Query::EventTidIs(event.thread.thread_id); - Query event_time = Query::EventTimeIs(event.timestamp); - Query event_duration = Query::EventDurationIs(duration); - Query event_phase = Query::EventPhaseIs(event.phase); - Query event_category = Query::EventCategoryIs(event.category); - Query event_name = Query::EventNameIs(event.name); - Query event_id = Query::EventIdIs(event.id); - Query event_has_arg1 = Query::EventHasNumberArg("num"); - Query event_has_arg2 = Query::EventHasStringArg("str"); - Query event_arg1 = - (Query::EventArg("num") == Query::Double(event.arg_numbers["num"])); - Query event_arg2 = - (Query::EventArg("str") == Query::String(event.arg_strings["str"])); - Query event_has_other = Query::EventHasOther(); - Query other_pid = Query::OtherPidIs(other.thread.process_id); - Query other_tid = Query::OtherTidIs(other.thread.thread_id); - Query other_time = Query::OtherTimeIs(other.timestamp); - Query other_phase = Query::OtherPhaseIs(other.phase); - Query other_category = Query::OtherCategoryIs(other.category); - Query other_name = Query::OtherNameIs(other.name); - Query other_id = Query::OtherIdIs(other.id); - Query other_has_arg1 = Query::OtherHasNumberArg("num2"); - Query other_has_arg2 = Query::OtherHasStringArg("str2"); - Query other_arg1 = - (Query::OtherArg("num2") == Query::Double(other.arg_numbers["num2"])); - Query other_arg2 = - (Query::OtherArg("str2") == Query::String(other.arg_strings["str2"])); - - EXPECT_TRUE(event_pid.Evaluate(event)); - EXPECT_TRUE(event_tid.Evaluate(event)); - EXPECT_TRUE(event_time.Evaluate(event)); - EXPECT_TRUE(event_duration.Evaluate(event)); - EXPECT_TRUE(event_phase.Evaluate(event)); - EXPECT_TRUE(event_category.Evaluate(event)); - EXPECT_TRUE(event_name.Evaluate(event)); - EXPECT_TRUE(event_id.Evaluate(event)); - EXPECT_TRUE(event_has_arg1.Evaluate(event)); - EXPECT_TRUE(event_has_arg2.Evaluate(event)); - EXPECT_TRUE(event_arg1.Evaluate(event)); - EXPECT_TRUE(event_arg2.Evaluate(event)); - EXPECT_TRUE(event_has_other.Evaluate(event)); - EXPECT_TRUE(other_pid.Evaluate(event)); - EXPECT_TRUE(other_tid.Evaluate(event)); - EXPECT_TRUE(other_time.Evaluate(event)); - EXPECT_TRUE(other_phase.Evaluate(event)); - EXPECT_TRUE(other_category.Evaluate(event)); - EXPECT_TRUE(other_name.Evaluate(event)); - EXPECT_TRUE(other_id.Evaluate(event)); - EXPECT_TRUE(other_has_arg1.Evaluate(event)); - EXPECT_TRUE(other_has_arg2.Evaluate(event)); - EXPECT_TRUE(other_arg1.Evaluate(event)); - EXPECT_TRUE(other_arg2.Evaluate(event)); - - // Evaluate event queries against other to verify the queries fail when the - // event members are wrong. - EXPECT_FALSE(event_pid.Evaluate(other)); - EXPECT_FALSE(event_tid.Evaluate(other)); - EXPECT_FALSE(event_time.Evaluate(other)); - EXPECT_FALSE(event_duration.Evaluate(other)); - EXPECT_FALSE(event_phase.Evaluate(other)); - EXPECT_FALSE(event_category.Evaluate(other)); - EXPECT_FALSE(event_name.Evaluate(other)); - EXPECT_FALSE(event_id.Evaluate(other)); - EXPECT_FALSE(event_has_arg1.Evaluate(other)); - EXPECT_FALSE(event_has_arg2.Evaluate(other)); - EXPECT_FALSE(event_arg1.Evaluate(other)); - EXPECT_FALSE(event_arg2.Evaluate(other)); - EXPECT_FALSE(event_has_other.Evaluate(other)); -} - -TEST_F(TraceEventAnalyzerTest, BooleanOperators) { - ManualSetUp(); - - BeginTracing(); - { - TRACE_EVENT_INSTANT1("cat1", "name1", TRACE_EVENT_SCOPE_THREAD, "num", 1); - TRACE_EVENT_INSTANT1("cat1", "name2", TRACE_EVENT_SCOPE_THREAD, "num", 2); - TRACE_EVENT_INSTANT1("cat2", "name3", TRACE_EVENT_SCOPE_THREAD, "num", 3); - TRACE_EVENT_INSTANT1("cat2", "name4", TRACE_EVENT_SCOPE_THREAD, "num", 4); - } - EndTracing(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer); - analyzer->SetIgnoreMetadataEvents(true); - - TraceEventVector found; - - // == - - analyzer->FindEvents(Query::EventCategory() == Query::String("cat1"), &found); - ASSERT_EQ(2u, found.size()); - EXPECT_STREQ("name1", found[0]->name.c_str()); - EXPECT_STREQ("name2", found[1]->name.c_str()); - - analyzer->FindEvents(Query::EventArg("num") == Query::Int(2), &found); - ASSERT_EQ(1u, found.size()); - EXPECT_STREQ("name2", found[0]->name.c_str()); - - // != - - analyzer->FindEvents(Query::EventCategory() != Query::String("cat1"), &found); - ASSERT_EQ(2u, found.size()); - EXPECT_STREQ("name3", found[0]->name.c_str()); - EXPECT_STREQ("name4", found[1]->name.c_str()); - - analyzer->FindEvents(Query::EventArg("num") != Query::Int(2), &found); - ASSERT_EQ(3u, found.size()); - EXPECT_STREQ("name1", found[0]->name.c_str()); - EXPECT_STREQ("name3", found[1]->name.c_str()); - EXPECT_STREQ("name4", found[2]->name.c_str()); - - // < - analyzer->FindEvents(Query::EventArg("num") < Query::Int(2), &found); - ASSERT_EQ(1u, found.size()); - EXPECT_STREQ("name1", found[0]->name.c_str()); - - // <= - analyzer->FindEvents(Query::EventArg("num") <= Query::Int(2), &found); - ASSERT_EQ(2u, found.size()); - EXPECT_STREQ("name1", found[0]->name.c_str()); - EXPECT_STREQ("name2", found[1]->name.c_str()); - - // > - analyzer->FindEvents(Query::EventArg("num") > Query::Int(3), &found); - ASSERT_EQ(1u, found.size()); - EXPECT_STREQ("name4", found[0]->name.c_str()); - - // >= - analyzer->FindEvents(Query::EventArg("num") >= Query::Int(4), &found); - ASSERT_EQ(1u, found.size()); - EXPECT_STREQ("name4", found[0]->name.c_str()); - - // && - analyzer->FindEvents(Query::EventName() != Query::String("name1") && - Query::EventArg("num") < Query::Int(3), &found); - ASSERT_EQ(1u, found.size()); - EXPECT_STREQ("name2", found[0]->name.c_str()); - - // || - analyzer->FindEvents(Query::EventName() == Query::String("name1") || - Query::EventArg("num") == Query::Int(3), &found); - ASSERT_EQ(2u, found.size()); - EXPECT_STREQ("name1", found[0]->name.c_str()); - EXPECT_STREQ("name3", found[1]->name.c_str()); - - // ! - analyzer->FindEvents(!(Query::EventName() == Query::String("name1") || - Query::EventArg("num") == Query::Int(3)), &found); - ASSERT_EQ(2u, found.size()); - EXPECT_STREQ("name2", found[0]->name.c_str()); - EXPECT_STREQ("name4", found[1]->name.c_str()); -} - -TEST_F(TraceEventAnalyzerTest, ArithmeticOperators) { - ManualSetUp(); - - BeginTracing(); - { - // These events are searched for: - TRACE_EVENT_INSTANT2("cat1", "math1", TRACE_EVENT_SCOPE_THREAD, - "a", 10, "b", 5); - TRACE_EVENT_INSTANT2("cat1", "math2", TRACE_EVENT_SCOPE_THREAD, - "a", 10, "b", 10); - // Extra events that never match, for noise: - TRACE_EVENT_INSTANT2("noise", "math3", TRACE_EVENT_SCOPE_THREAD, - "a", 1, "b", 3); - TRACE_EVENT_INSTANT2("noise", "math4", TRACE_EVENT_SCOPE_THREAD, - "c", 10, "d", 5); - } - EndTracing(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer.get()); - - TraceEventVector found; - - // Verify that arithmetic operators function: - - // + - analyzer->FindEvents(Query::EventArg("a") + Query::EventArg("b") == - Query::Int(20), &found); - EXPECT_EQ(1u, found.size()); - EXPECT_STREQ("math2", found.front()->name.c_str()); - - // - - analyzer->FindEvents(Query::EventArg("a") - Query::EventArg("b") == - Query::Int(5), &found); - EXPECT_EQ(1u, found.size()); - EXPECT_STREQ("math1", found.front()->name.c_str()); - - // * - analyzer->FindEvents(Query::EventArg("a") * Query::EventArg("b") == - Query::Int(50), &found); - EXPECT_EQ(1u, found.size()); - EXPECT_STREQ("math1", found.front()->name.c_str()); - - // / - analyzer->FindEvents(Query::EventArg("a") / Query::EventArg("b") == - Query::Int(2), &found); - EXPECT_EQ(1u, found.size()); - EXPECT_STREQ("math1", found.front()->name.c_str()); - - // % - analyzer->FindEvents(Query::EventArg("a") % Query::EventArg("b") == - Query::Int(0), &found); - EXPECT_EQ(2u, found.size()); - - // - (negate) - analyzer->FindEvents(-Query::EventArg("b") == Query::Int(-10), &found); - EXPECT_EQ(1u, found.size()); - EXPECT_STREQ("math2", found.front()->name.c_str()); -} - -TEST_F(TraceEventAnalyzerTest, StringPattern) { - ManualSetUp(); - - BeginTracing(); - { - TRACE_EVENT_INSTANT0("cat1", "name1", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("cat1", "name2", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("cat1", "no match", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("cat1", "name3x", TRACE_EVENT_SCOPE_THREAD); - } - EndTracing(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer.get()); - analyzer->SetIgnoreMetadataEvents(true); - - TraceEventVector found; - - analyzer->FindEvents(Query::EventName() == Query::Pattern("name?"), &found); - ASSERT_EQ(2u, found.size()); - EXPECT_STREQ("name1", found[0]->name.c_str()); - EXPECT_STREQ("name2", found[1]->name.c_str()); - - analyzer->FindEvents(Query::EventName() == Query::Pattern("name*"), &found); - ASSERT_EQ(3u, found.size()); - EXPECT_STREQ("name1", found[0]->name.c_str()); - EXPECT_STREQ("name2", found[1]->name.c_str()); - EXPECT_STREQ("name3x", found[2]->name.c_str()); - - analyzer->FindEvents(Query::EventName() != Query::Pattern("name*"), &found); - ASSERT_EQ(1u, found.size()); - EXPECT_STREQ("no match", found[0]->name.c_str()); -} - -// Test that duration queries work. -TEST_F(TraceEventAnalyzerTest, BeginEndDuration) { - ManualSetUp(); - - const base::TimeDelta kSleepTime = base::TimeDelta::FromMilliseconds(200); - // We will search for events that have a duration of greater than 90% of the - // sleep time, so that there is no flakiness. - int64_t duration_cutoff_us = (kSleepTime.InMicroseconds() * 9) / 10; - - BeginTracing(); - { - TRACE_EVENT_BEGIN0("cat1", "name1"); // found by duration query - TRACE_EVENT_BEGIN0("noise", "name2"); // not searched for, just noise - { - TRACE_EVENT_BEGIN0("cat2", "name3"); // found by duration query - // next event not searched for, just noise - TRACE_EVENT_INSTANT0("noise", "name4", TRACE_EVENT_SCOPE_THREAD); - base::PlatformThread::Sleep(kSleepTime); - TRACE_EVENT_BEGIN0("cat2", "name5"); // not found (duration too short) - TRACE_EVENT_END0("cat2", "name5"); // not found (duration too short) - TRACE_EVENT_END0("cat2", "name3"); // found by duration query - } - TRACE_EVENT_END0("noise", "name2"); // not searched for, just noise - TRACE_EVENT_END0("cat1", "name1"); // found by duration query - } - EndTracing(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer.get()); - analyzer->AssociateBeginEndEvents(); - - TraceEventVector found; - analyzer->FindEvents( - Query::MatchBeginWithEnd() && - Query::EventDuration() > - Query::Int(static_cast(duration_cutoff_us)) && - (Query::EventCategory() == Query::String("cat1") || - Query::EventCategory() == Query::String("cat2") || - Query::EventCategory() == Query::String("cat3")), - &found); - ASSERT_EQ(2u, found.size()); - EXPECT_STREQ("name1", found[0]->name.c_str()); - EXPECT_STREQ("name3", found[1]->name.c_str()); -} - -// Test that duration queries work. -TEST_F(TraceEventAnalyzerTest, CompleteDuration) { - ManualSetUp(); - - const base::TimeDelta kSleepTime = base::TimeDelta::FromMilliseconds(200); - // We will search for events that have a duration of greater than 90% of the - // sleep time, so that there is no flakiness. - int64_t duration_cutoff_us = (kSleepTime.InMicroseconds() * 9) / 10; - - BeginTracing(); - { - TRACE_EVENT0("cat1", "name1"); // found by duration query - TRACE_EVENT0("noise", "name2"); // not searched for, just noise - { - TRACE_EVENT0("cat2", "name3"); // found by duration query - // next event not searched for, just noise - TRACE_EVENT_INSTANT0("noise", "name4", TRACE_EVENT_SCOPE_THREAD); - base::PlatformThread::Sleep(kSleepTime); - TRACE_EVENT0("cat2", "name5"); // not found (duration too short) - } - } - EndTracing(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer.get()); - analyzer->AssociateBeginEndEvents(); - - TraceEventVector found; - analyzer->FindEvents( - Query::EventCompleteDuration() > - Query::Int(static_cast(duration_cutoff_us)) && - (Query::EventCategory() == Query::String("cat1") || - Query::EventCategory() == Query::String("cat2") || - Query::EventCategory() == Query::String("cat3")), - &found); - ASSERT_EQ(2u, found.size()); - EXPECT_STREQ("name1", found[0]->name.c_str()); - EXPECT_STREQ("name3", found[1]->name.c_str()); -} - -// Test AssociateBeginEndEvents -TEST_F(TraceEventAnalyzerTest, BeginEndAssocations) { - ManualSetUp(); - - BeginTracing(); - { - TRACE_EVENT_END0("cat1", "name1"); // does not match out of order begin - TRACE_EVENT_BEGIN0("cat1", "name2"); - TRACE_EVENT_INSTANT0("cat1", "name3", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_BEGIN0("cat1", "name1"); - TRACE_EVENT_END0("cat1", "name2"); - } - EndTracing(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer.get()); - analyzer->AssociateBeginEndEvents(); - - TraceEventVector found; - analyzer->FindEvents(Query::MatchBeginWithEnd(), &found); - ASSERT_EQ(1u, found.size()); - EXPECT_STREQ("name2", found[0]->name.c_str()); -} - -// Test MergeAssociatedEventArgs -TEST_F(TraceEventAnalyzerTest, MergeAssociatedEventArgs) { - ManualSetUp(); - - const char arg_string[] = "arg_string"; - BeginTracing(); - { - TRACE_EVENT_BEGIN0("cat1", "name1"); - TRACE_EVENT_END1("cat1", "name1", "arg", arg_string); - } - EndTracing(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer.get()); - analyzer->AssociateBeginEndEvents(); - - TraceEventVector found; - analyzer->FindEvents(Query::MatchBeginName("name1"), &found); - ASSERT_EQ(1u, found.size()); - std::string arg_actual; - EXPECT_FALSE(found[0]->GetArgAsString("arg", &arg_actual)); - - analyzer->MergeAssociatedEventArgs(); - EXPECT_TRUE(found[0]->GetArgAsString("arg", &arg_actual)); - EXPECT_STREQ(arg_string, arg_actual.c_str()); -} - -// Test AssociateAsyncBeginEndEvents -TEST_F(TraceEventAnalyzerTest, AsyncBeginEndAssocations) { - ManualSetUp(); - - BeginTracing(); - { - TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xA); // no match / out of order - TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xB); - TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xC); - TRACE_EVENT_INSTANT0("cat1", "name1", TRACE_EVENT_SCOPE_THREAD); // noise - TRACE_EVENT0("cat1", "name1"); // noise - TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xB); - TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xC); - TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xA); // no match / out of order - } - EndTracing(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer.get()); - analyzer->AssociateAsyncBeginEndEvents(); - - TraceEventVector found; - analyzer->FindEvents(Query::MatchAsyncBeginWithNext(), &found); - ASSERT_EQ(2u, found.size()); - EXPECT_STRCASEEQ("0xb", found[0]->id.c_str()); - EXPECT_STRCASEEQ("0xc", found[1]->id.c_str()); -} - -// Test AssociateAsyncBeginEndEvents -TEST_F(TraceEventAnalyzerTest, AsyncBeginEndAssocationsWithSteps) { - ManualSetUp(); - - BeginTracing(); - { - TRACE_EVENT_ASYNC_STEP_INTO0("c", "n", 0xA, "s1"); - TRACE_EVENT_ASYNC_END0("c", "n", 0xA); - TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xB); - TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xC); - TRACE_EVENT_ASYNC_STEP_PAST0("c", "n", 0xB, "s1"); - TRACE_EVENT_ASYNC_STEP_INTO0("c", "n", 0xC, "s1"); - TRACE_EVENT_ASYNC_STEP_INTO1("c", "n", 0xC, "s2", "a", 1); - TRACE_EVENT_ASYNC_END0("c", "n", 0xB); - TRACE_EVENT_ASYNC_END0("c", "n", 0xC); - TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xA); - TRACE_EVENT_ASYNC_STEP_INTO0("c", "n", 0xA, "s2"); - } - EndTracing(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer.get()); - analyzer->AssociateAsyncBeginEndEvents(); - - TraceEventVector found; - analyzer->FindEvents(Query::MatchAsyncBeginWithNext(), &found); - ASSERT_EQ(3u, found.size()); - - EXPECT_STRCASEEQ("0xb", found[0]->id.c_str()); - EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_PAST, found[0]->other_event->phase); - EXPECT_EQ(found[0], found[0]->other_event->prev_event); - EXPECT_TRUE(found[0]->other_event->other_event); - EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_END, - found[0]->other_event->other_event->phase); - EXPECT_EQ(found[0]->other_event, - found[0]->other_event->other_event->prev_event); - - EXPECT_STRCASEEQ("0xc", found[1]->id.c_str()); - EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_INTO, found[1]->other_event->phase); - EXPECT_EQ(found[1], found[1]->other_event->prev_event); - EXPECT_TRUE(found[1]->other_event->other_event); - EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_INTO, - found[1]->other_event->other_event->phase); - EXPECT_EQ(found[1]->other_event, - found[1]->other_event->other_event->prev_event); - double arg_actual = 0; - EXPECT_TRUE(found[1]->other_event->other_event->GetArgAsNumber( - "a", &arg_actual)); - EXPECT_EQ(1.0, arg_actual); - EXPECT_TRUE(found[1]->other_event->other_event->other_event); - EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_END, - found[1]->other_event->other_event->other_event->phase); - - EXPECT_STRCASEEQ("0xa", found[2]->id.c_str()); - EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_INTO, found[2]->other_event->phase); -} - -// Test that the TraceAnalyzer custom associations work. -TEST_F(TraceEventAnalyzerTest, CustomAssociations) { - ManualSetUp(); - - // Add events that begin/end in pipelined ordering with unique ID parameter - // to match up the begin/end pairs. - BeginTracing(); - { - // no begin match - TRACE_EVENT_INSTANT1("cat1", "end", TRACE_EVENT_SCOPE_THREAD, "id", 1); - // end is cat4 - TRACE_EVENT_INSTANT1("cat2", "begin", TRACE_EVENT_SCOPE_THREAD, "id", 2); - // end is cat5 - TRACE_EVENT_INSTANT1("cat3", "begin", TRACE_EVENT_SCOPE_THREAD, "id", 3); - TRACE_EVENT_INSTANT1("cat4", "end", TRACE_EVENT_SCOPE_THREAD, "id", 2); - TRACE_EVENT_INSTANT1("cat5", "end", TRACE_EVENT_SCOPE_THREAD, "id", 3); - // no end match - TRACE_EVENT_INSTANT1("cat6", "begin", TRACE_EVENT_SCOPE_THREAD, "id", 1); - } - EndTracing(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer.get()); - - // begin, end, and match queries to find proper begin/end pairs. - Query begin(Query::EventName() == Query::String("begin")); - Query end(Query::EventName() == Query::String("end")); - Query match(Query::EventArg("id") == Query::OtherArg("id")); - analyzer->AssociateEvents(begin, end, match); - - TraceEventVector found; - - // cat1 has no other_event. - analyzer->FindEvents(Query::EventCategory() == Query::String("cat1") && - Query::EventHasOther(), &found); - EXPECT_EQ(0u, found.size()); - - // cat1 has no other_event. - analyzer->FindEvents(Query::EventCategory() == Query::String("cat1") && - !Query::EventHasOther(), &found); - EXPECT_EQ(1u, found.size()); - - // cat6 has no other_event. - analyzer->FindEvents(Query::EventCategory() == Query::String("cat6") && - !Query::EventHasOther(), &found); - EXPECT_EQ(1u, found.size()); - - // cat2 and cat4 are associated. - analyzer->FindEvents(Query::EventCategory() == Query::String("cat2") && - Query::OtherCategory() == Query::String("cat4"), &found); - EXPECT_EQ(1u, found.size()); - - // cat4 and cat2 are not associated. - analyzer->FindEvents(Query::EventCategory() == Query::String("cat4") && - Query::OtherCategory() == Query::String("cat2"), &found); - EXPECT_EQ(0u, found.size()); - - // cat3 and cat5 are associated. - analyzer->FindEvents(Query::EventCategory() == Query::String("cat3") && - Query::OtherCategory() == Query::String("cat5"), &found); - EXPECT_EQ(1u, found.size()); - - // cat5 and cat3 are not associated. - analyzer->FindEvents(Query::EventCategory() == Query::String("cat5") && - Query::OtherCategory() == Query::String("cat3"), &found); - EXPECT_EQ(0u, found.size()); -} - -// Verify that Query literals and types are properly casted. -TEST_F(TraceEventAnalyzerTest, Literals) { - ManualSetUp(); - - // Since these queries don't refer to the event data, the dummy event below - // will never be accessed. - TraceEvent dummy; - char char_num = 5; - short short_num = -5; - EXPECT_TRUE((Query::Double(5.0) == Query::Int(char_num)).Evaluate(dummy)); - EXPECT_TRUE((Query::Double(-5.0) == Query::Int(short_num)).Evaluate(dummy)); - EXPECT_TRUE((Query::Double(1.0) == Query::Uint(1u)).Evaluate(dummy)); - EXPECT_TRUE((Query::Double(1.0) == Query::Int(1)).Evaluate(dummy)); - EXPECT_TRUE((Query::Double(-1.0) == Query::Int(-1)).Evaluate(dummy)); - EXPECT_TRUE((Query::Double(1.0) == Query::Double(1.0f)).Evaluate(dummy)); - EXPECT_TRUE((Query::Bool(true) == Query::Int(1)).Evaluate(dummy)); - EXPECT_TRUE((Query::Bool(false) == Query::Int(0)).Evaluate(dummy)); - EXPECT_TRUE((Query::Bool(true) == Query::Double(1.0f)).Evaluate(dummy)); - EXPECT_TRUE((Query::Bool(false) == Query::Double(0.0f)).Evaluate(dummy)); -} - -// Test GetRateStats. -TEST_F(TraceEventAnalyzerTest, RateStats) { - std::vector events; - events.reserve(100); - TraceEventVector event_ptrs; - double timestamp = 0.0; - double little_delta = 1.0; - double big_delta = 10.0; - double tiny_delta = 0.1; - RateStats stats; - RateStatsOptions options; - - // Insert 10 events, each apart by little_delta. - for (int i = 0; i < 10; ++i) { - timestamp += little_delta; - TraceEvent event; - event.timestamp = timestamp; - events.push_back(std::move(event)); - event_ptrs.push_back(&events.back()); - } - - ASSERT_TRUE(GetRateStats(event_ptrs, &stats, NULL)); - EXPECT_EQ(little_delta, stats.mean_us); - EXPECT_EQ(little_delta, stats.min_us); - EXPECT_EQ(little_delta, stats.max_us); - EXPECT_EQ(0.0, stats.standard_deviation_us); - - // Add an event apart by big_delta. - { - timestamp += big_delta; - TraceEvent event; - event.timestamp = timestamp; - events.push_back(std::move(event)); - event_ptrs.push_back(&events.back()); - } - - ASSERT_TRUE(GetRateStats(event_ptrs, &stats, NULL)); - EXPECT_LT(little_delta, stats.mean_us); - EXPECT_EQ(little_delta, stats.min_us); - EXPECT_EQ(big_delta, stats.max_us); - EXPECT_LT(0.0, stats.standard_deviation_us); - - // Trim off the biggest delta and verify stats. - options.trim_min = 0; - options.trim_max = 1; - ASSERT_TRUE(GetRateStats(event_ptrs, &stats, &options)); - EXPECT_EQ(little_delta, stats.mean_us); - EXPECT_EQ(little_delta, stats.min_us); - EXPECT_EQ(little_delta, stats.max_us); - EXPECT_EQ(0.0, stats.standard_deviation_us); - - // Add an event apart by tiny_delta. - { - timestamp += tiny_delta; - TraceEvent event; - event.timestamp = timestamp; - events.push_back(std::move(event)); - event_ptrs.push_back(&events.back()); - } - - // Trim off both the biggest and tiniest delta and verify stats. - options.trim_min = 1; - options.trim_max = 1; - ASSERT_TRUE(GetRateStats(event_ptrs, &stats, &options)); - EXPECT_EQ(little_delta, stats.mean_us); - EXPECT_EQ(little_delta, stats.min_us); - EXPECT_EQ(little_delta, stats.max_us); - EXPECT_EQ(0.0, stats.standard_deviation_us); - - // Verify smallest allowed number of events. - { - TraceEvent event; - TraceEventVector few_event_ptrs; - few_event_ptrs.push_back(&event); - few_event_ptrs.push_back(&event); - ASSERT_FALSE(GetRateStats(few_event_ptrs, &stats, NULL)); - few_event_ptrs.push_back(&event); - ASSERT_TRUE(GetRateStats(few_event_ptrs, &stats, NULL)); - - // Trim off more than allowed and verify failure. - options.trim_min = 0; - options.trim_max = 1; - ASSERT_FALSE(GetRateStats(few_event_ptrs, &stats, &options)); - } -} - -// Test FindFirstOf and FindLastOf. -TEST_F(TraceEventAnalyzerTest, FindOf) { - size_t num_events = 100; - size_t index = 0; - TraceEventVector event_ptrs; - EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(true), 0, &index)); - EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(true), 10, &index)); - EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(true), 0, &index)); - EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(true), 10, &index)); - - std::vector events; - events.resize(num_events); - for (size_t i = 0; i < events.size(); ++i) - event_ptrs.push_back(&events[i]); - size_t bam_index = num_events/2; - events[bam_index].name = "bam"; - Query query_bam = Query::EventName() == Query::String(events[bam_index].name); - - // FindFirstOf - EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(false), 0, &index)); - EXPECT_TRUE(FindFirstOf(event_ptrs, Query::Bool(true), 0, &index)); - EXPECT_EQ(0u, index); - EXPECT_TRUE(FindFirstOf(event_ptrs, Query::Bool(true), 5, &index)); - EXPECT_EQ(5u, index); - - EXPECT_FALSE(FindFirstOf(event_ptrs, query_bam, bam_index + 1, &index)); - EXPECT_TRUE(FindFirstOf(event_ptrs, query_bam, 0, &index)); - EXPECT_EQ(bam_index, index); - EXPECT_TRUE(FindFirstOf(event_ptrs, query_bam, bam_index, &index)); - EXPECT_EQ(bam_index, index); - - // FindLastOf - EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(false), 1000, &index)); - EXPECT_TRUE(FindLastOf(event_ptrs, Query::Bool(true), 1000, &index)); - EXPECT_EQ(num_events - 1, index); - EXPECT_TRUE(FindLastOf(event_ptrs, Query::Bool(true), num_events - 5, - &index)); - EXPECT_EQ(num_events - 5, index); - - EXPECT_FALSE(FindLastOf(event_ptrs, query_bam, bam_index - 1, &index)); - EXPECT_TRUE(FindLastOf(event_ptrs, query_bam, num_events, &index)); - EXPECT_EQ(bam_index, index); - EXPECT_TRUE(FindLastOf(event_ptrs, query_bam, bam_index, &index)); - EXPECT_EQ(bam_index, index); -} - -// Test FindClosest. -TEST_F(TraceEventAnalyzerTest, FindClosest) { - size_t index_1 = 0; - size_t index_2 = 0; - TraceEventVector event_ptrs; - EXPECT_FALSE(FindClosest(event_ptrs, Query::Bool(true), 0, - &index_1, &index_2)); - - size_t num_events = 5; - std::vector events; - events.resize(num_events); - for (size_t i = 0; i < events.size(); ++i) { - // timestamps go up exponentially so the lower index is always closer in - // time than the higher index. - events[i].timestamp = static_cast(i) * static_cast(i); - event_ptrs.push_back(&events[i]); - } - events[0].name = "one"; - events[2].name = "two"; - events[4].name = "three"; - Query query_named = Query::EventName() != Query::String(std::string()); - Query query_one = Query::EventName() == Query::String("one"); - - // Only one event matches query_one, so two closest can't be found. - EXPECT_FALSE(FindClosest(event_ptrs, query_one, 0, &index_1, &index_2)); - - EXPECT_TRUE(FindClosest(event_ptrs, query_one, 3, &index_1, NULL)); - EXPECT_EQ(0u, index_1); - - EXPECT_TRUE(FindClosest(event_ptrs, query_named, 1, &index_1, &index_2)); - EXPECT_EQ(0u, index_1); - EXPECT_EQ(2u, index_2); - - EXPECT_TRUE(FindClosest(event_ptrs, query_named, 4, &index_1, &index_2)); - EXPECT_EQ(4u, index_1); - EXPECT_EQ(2u, index_2); - - EXPECT_TRUE(FindClosest(event_ptrs, query_named, 3, &index_1, &index_2)); - EXPECT_EQ(2u, index_1); - EXPECT_EQ(0u, index_2); -} - -// Test CountMatches. -TEST_F(TraceEventAnalyzerTest, CountMatches) { - TraceEventVector event_ptrs; - EXPECT_EQ(0u, CountMatches(event_ptrs, Query::Bool(true), 0, 10)); - - size_t num_events = 5; - size_t num_named = 3; - std::vector events; - events.resize(num_events); - for (size_t i = 0; i < events.size(); ++i) - event_ptrs.push_back(&events[i]); - events[0].name = "one"; - events[2].name = "two"; - events[4].name = "three"; - Query query_named = Query::EventName() != Query::String(std::string()); - Query query_one = Query::EventName() == Query::String("one"); - - EXPECT_EQ(0u, CountMatches(event_ptrs, Query::Bool(false))); - EXPECT_EQ(num_events, CountMatches(event_ptrs, Query::Bool(true))); - EXPECT_EQ(num_events - 1, CountMatches(event_ptrs, Query::Bool(true), - 1, num_events)); - EXPECT_EQ(1u, CountMatches(event_ptrs, query_one)); - EXPECT_EQ(num_events - 1, CountMatches(event_ptrs, !query_one)); - EXPECT_EQ(num_named, CountMatches(event_ptrs, query_named)); -} - -TEST_F(TraceEventAnalyzerTest, ComplexArgument) { - ManualSetUp(); - - BeginTracing(); - { - std::unique_ptr value( - new base::trace_event::TracedValue); - value->SetString("property", "value"); - TRACE_EVENT1("cat", "name", "arg", std::move(value)); - } - EndTracing(); - - std::unique_ptr analyzer( - TraceAnalyzer::Create(output_.json_output)); - ASSERT_TRUE(analyzer.get()); - - TraceEventVector events; - analyzer->FindEvents(Query::EventName() == Query::String("name"), &events); - - EXPECT_EQ(1u, events.size()); - EXPECT_EQ("cat", events[0]->category); - EXPECT_EQ("name", events[0]->name); - EXPECT_TRUE(events[0]->HasArg("arg")); - - std::unique_ptr arg; - events[0]->GetArgAsValue("arg", &arg); - base::DictionaryValue* arg_dict; - EXPECT_TRUE(arg->GetAsDictionary(&arg_dict)); - std::string property; - EXPECT_TRUE(arg_dict->GetString("property", &property)); - EXPECT_EQ("value", property); -} - -} // namespace trace_analyzer diff --git a/base/third_party/valgrind/memcheck.h b/base/third_party/valgrind/memcheck.h index aac34fcaf630cacaa0ca91cf6e5b6f521d8c8dcd..f59c21223a8a5ed0be2eab968e3fb50963c06073 100644 --- a/base/third_party/valgrind/memcheck.h +++ b/base/third_party/valgrind/memcheck.h @@ -1,6 +1,4 @@ -#ifdef ANDROID - #include "memcheck/memcheck.h" -#else + /* ---------------------------------------------------------------- @@ -279,4 +277,3 @@ typedef #endif -#endif diff --git a/base/third_party/valgrind/valgrind.h b/base/third_party/valgrind/valgrind.h index 0668a710cd01c66217669c8e4460482cb578474e..0bae0aa130eec3cf426bdff3bdd3880c7b308158 100644 --- a/base/third_party/valgrind/valgrind.h +++ b/base/third_party/valgrind/valgrind.h @@ -1,6 +1,3 @@ -#ifdef ANDROID - #include "include/valgrind.h" -#else /* -*- c -*- ---------------------------------------------------------------- @@ -4793,5 +4790,3 @@ VALGRIND_PRINTF_BACKTRACE(const char *format, ...) #undef PLAT_ppc64_aix5 #endif /* __VALGRIND_H */ - -#endif diff --git a/base/threading/thread_id_name_manager.cc b/base/threading/thread_id_name_manager.cc index 107e0dc498573a3c1777c41048954b285f819271..c0ca42da30462e85adacdb6e4a4f19411a1711a7 100644 --- a/base/threading/thread_id_name_manager.cc +++ b/base/threading/thread_id_name_manager.cc @@ -10,7 +10,8 @@ #include "base/logging.h" #include "base/memory/singleton.h" #include "base/strings/string_util.h" -#include "base/trace_event/heap_profiler_allocation_context_tracker.h" +// Unsupported in libchrome. +// #include "base/trace_event/heap_profiler_allocation_context_tracker.h" namespace base { namespace { @@ -80,8 +81,9 @@ void ThreadIdNameManager::SetName(PlatformThreadId id, // call GetName(which holds a lock) during the first allocation because it can // cause a deadlock when the first allocation happens in the // ThreadIdNameManager itself when holding the lock. - trace_event::AllocationContextTracker::SetCurrentThreadName( - leaked_str->c_str()); + // Unsupported in libchrome. + // trace_event::AllocationContextTracker::SetCurrentThreadName( + // leaked_str->c_str()); } const char* ThreadIdNameManager::GetName(PlatformThreadId id) { diff --git a/base/trace_event/category_registry.cc b/base/trace_event/category_registry.cc deleted file mode 100644 index e7c14606d66bd997cb3b5fb0ce4df19a2996d6f3..0000000000000000000000000000000000000000 --- a/base/trace_event/category_registry.cc +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2016 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. - -#include "base/trace_event/category_registry.h" - -#include - -#include - -#include "base/atomicops.h" -#include "base/debug/leak_annotations.h" -#include "base/logging.h" -#include "base/third_party/dynamic_annotations/dynamic_annotations.h" -#include "base/trace_event/trace_category.h" - -namespace base { -namespace trace_event { - -namespace { - -constexpr size_t kMaxCategories = 200; -const int kNumBuiltinCategories = 4; - -// |g_categories| might end up causing creating dynamic initializers if not POD. -static_assert(std::is_pod::value, "TraceCategory must be POD"); - -// These entries must be kept consistent with the kCategory* consts below. -TraceCategory g_categories[kMaxCategories] = { - {0, 0, "tracing categories exhausted; must increase kMaxCategories"}, - {0, 0, "tracing already shutdown"}, // See kCategoryAlreadyShutdown below. - {0, 0, "__metadata"}, // See kCategoryMetadata below. - {0, 0, "toplevel"}, // Warmup the toplevel category. -}; - -base::subtle::AtomicWord g_category_index = kNumBuiltinCategories; - -bool IsValidCategoryPtr(const TraceCategory* category) { - // If any of these are hit, something has cached a corrupt category pointer. - uintptr_t ptr = reinterpret_cast(category); - return ptr % sizeof(void*) == 0 && - ptr >= reinterpret_cast(&g_categories[0]) && - ptr <= reinterpret_cast(&g_categories[kMaxCategories - 1]); -} - -} // namespace - -// static -TraceCategory* const CategoryRegistry::kCategoryExhausted = &g_categories[0]; -TraceCategory* const CategoryRegistry::kCategoryAlreadyShutdown = - &g_categories[1]; -TraceCategory* const CategoryRegistry::kCategoryMetadata = &g_categories[2]; - -// static -void CategoryRegistry::Initialize() { - // Trace is enabled or disabled on one thread while other threads are - // accessing the enabled flag. We don't care whether edge-case events are - // traced or not, so we allow races on the enabled flag to keep the trace - // macros fast. - for (size_t i = 0; i < kMaxCategories; ++i) { - ANNOTATE_BENIGN_RACE(g_categories[i].state_ptr(), - "trace_event category enabled"); - // If this DCHECK is hit in a test it means that ResetForTesting() is not - // called and the categories state leaks between test fixtures. - DCHECK(!g_categories[i].is_enabled()); - } -} - -// static -void CategoryRegistry::ResetForTesting() { - // reset_for_testing clears up only the enabled state and filters. The - // categories themselves cannot be cleared up because the static pointers - // injected by the macros still point to them and cannot be reset. - for (size_t i = 0; i < kMaxCategories; ++i) - g_categories[i].reset_for_testing(); -} - -// static -TraceCategory* CategoryRegistry::GetCategoryByName(const char* category_name) { - DCHECK(!strchr(category_name, '"')) - << "Category names may not contain double quote"; - - // The g_categories is append only, avoid using a lock for the fast path. - size_t category_index = base::subtle::Acquire_Load(&g_category_index); - - // Search for pre-existing category group. - for (size_t i = 0; i < category_index; ++i) { - if (strcmp(g_categories[i].name(), category_name) == 0) { - return &g_categories[i]; - } - } - return nullptr; -} - -bool CategoryRegistry::GetOrCreateCategoryLocked( - const char* category_name, - CategoryInitializerFn category_initializer_fn, - TraceCategory** category) { - // This is the slow path: the lock is not held in the fastpath - // (GetCategoryByName), so more than one thread could have reached here trying - // to add the same category. - *category = GetCategoryByName(category_name); - if (*category) - return false; - - // Create a new category. - size_t category_index = base::subtle::Acquire_Load(&g_category_index); - if (category_index >= kMaxCategories) { - NOTREACHED() << "must increase kMaxCategories"; - *category = kCategoryExhausted; - return false; - } - - // TODO(primiano): this strdup should be removed. The only documented reason - // for it was TraceWatchEvent, which is gone. However, something might have - // ended up relying on this. Needs some auditing before removal. - const char* category_name_copy = strdup(category_name); - ANNOTATE_LEAKING_OBJECT_PTR(category_name_copy); - - *category = &g_categories[category_index]; - DCHECK(!(*category)->is_valid()); - DCHECK(!(*category)->is_enabled()); - (*category)->set_name(category_name_copy); - category_initializer_fn(*category); - - // Update the max index now. - base::subtle::Release_Store(&g_category_index, category_index + 1); - return true; -} - -// static -const TraceCategory* CategoryRegistry::GetCategoryByStatePtr( - const uint8_t* category_state) { - const TraceCategory* category = TraceCategory::FromStatePtr(category_state); - DCHECK(IsValidCategoryPtr(category)); - return category; -} - -// static -bool CategoryRegistry::IsBuiltinCategory(const TraceCategory* category) { - DCHECK(IsValidCategoryPtr(category)); - return category < &g_categories[kNumBuiltinCategories]; -} - -// static -CategoryRegistry::Range CategoryRegistry::GetAllCategories() { - // The |g_categories| array is append only. We have to only guarantee to - // not return an index to a category which is being initialized by - // GetOrCreateCategoryByName(). - size_t category_index = base::subtle::Acquire_Load(&g_category_index); - return CategoryRegistry::Range(&g_categories[0], - &g_categories[category_index]); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/category_registry.h b/base/trace_event/category_registry.h deleted file mode 100644 index 9c08efa3e14d80d71dc1fe5f3a910d34dca9cbcd..0000000000000000000000000000000000000000 --- a/base/trace_event/category_registry.h +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2016 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 BASE_TRACE_EVENT_CATEGORY_REGISTRY_H_ -#define BASE_TRACE_EVENT_CATEGORY_REGISTRY_H_ - -#include -#include - -#include "base/base_export.h" -#include "base/logging.h" - -namespace base { -namespace trace_event { - -struct TraceCategory; -class TraceCategoryTest; -class TraceLog; - -// Allows fast and thread-safe acces to the state of all tracing categories. -// All the methods in this class can be concurrently called on multiple threads, -// unless otherwise noted (e.g., GetOrCreateCategoryLocked). -// The reason why this is a fully static class with global state is to allow to -// statically define known categories as global linker-initialized structs, -// without requiring static initializers. -class BASE_EXPORT CategoryRegistry { - public: - // Allows for-each iterations over a slice of the categories array. - class Range { - public: - Range(TraceCategory* begin, TraceCategory* end) : begin_(begin), end_(end) { - DCHECK_LE(begin, end); - } - TraceCategory* begin() const { return begin_; } - TraceCategory* end() const { return end_; } - - private: - TraceCategory* const begin_; - TraceCategory* const end_; - }; - - // Known categories. - static TraceCategory* const kCategoryExhausted; - static TraceCategory* const kCategoryMetadata; - static TraceCategory* const kCategoryAlreadyShutdown; - - // Returns a category entry from the Category.state_ptr() pointer. - // TODO(primiano): trace macros should just keep a pointer to the entire - // TraceCategory, not just the enabled state pointer. That would remove the - // need for this function and make everything cleaner at no extra cost (as - // long as the |state_| is the first field of the struct, which can be - // guaranteed via static_assert, see TraceCategory ctor). - static const TraceCategory* GetCategoryByStatePtr( - const uint8_t* category_state); - - // Returns a category from its name or nullptr if not found. - // The output |category| argument is an undefinitely lived pointer to the - // TraceCategory owned by the registry. TRACE_EVENTx macros will cache this - // pointer and use it for checks in their fast-paths. - static TraceCategory* GetCategoryByName(const char* category_name); - - static bool IsBuiltinCategory(const TraceCategory*); - - private: - friend class TraceCategoryTest; - friend class TraceLog; - using CategoryInitializerFn = void (*)(TraceCategory*); - - // Only for debugging/testing purposes, is a no-op on release builds. - static void Initialize(); - - // Resets the state of all categories, to clear up the state between tests. - static void ResetForTesting(); - - // Used to get/create a category in the slow-path. If the category exists - // already, this has the same effect of GetCategoryByName and returns false. - // If not, a new category is created and the CategoryInitializerFn is invoked - // before retuning true. The caller must guarantee serialization: either call - // this method from a single thread or hold a lock when calling this. - static bool GetOrCreateCategoryLocked(const char* category_name, - CategoryInitializerFn, - TraceCategory**); - - // Allows to iterate over the valid categories in a for-each loop. - // This includes builtin categories such as __metadata. - static Range GetAllCategories(); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_CATEGORY_REGISTRY_H_ diff --git a/base/trace_event/event_name_filter.cc b/base/trace_event/event_name_filter.cc deleted file mode 100644 index 8d0058c147453d9cc9d5f6ff4d48990c114f5bc5..0000000000000000000000000000000000000000 --- a/base/trace_event/event_name_filter.cc +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2016 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. - -#include "base/trace_event/event_name_filter.h" - -#include "base/trace_event/trace_event_impl.h" - -namespace base { -namespace trace_event { - -// static -const char EventNameFilter::kName[] = "event_whitelist_predicate"; - -EventNameFilter::EventNameFilter( - std::unique_ptr event_names_whitelist) - : event_names_whitelist_(std::move(event_names_whitelist)) {} - -EventNameFilter::~EventNameFilter() {} - -bool EventNameFilter::FilterTraceEvent(const TraceEvent& trace_event) const { - return event_names_whitelist_->count(trace_event.name()) != 0; -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/event_name_filter.h b/base/trace_event/event_name_filter.h deleted file mode 100644 index 19333b3e03338d5d6981e71e0c4c3ce2b9073f26..0000000000000000000000000000000000000000 --- a/base/trace_event/event_name_filter.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2016 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 BASE_TRACE_EVENT_EVENT_NAME_FILTER_H_ -#define BASE_TRACE_EVENT_EVENT_NAME_FILTER_H_ - -#include -#include -#include - -#include "base/base_export.h" -#include "base/macros.h" -#include "base/trace_event/trace_event_filter.h" - -namespace base { -namespace trace_event { - -class TraceEvent; - -// Filters trace events by checking the full name against a whitelist. -// The current implementation is quite simple and dumb and just uses a -// hashtable which requires char* to std::string conversion. It could be smarter -// and use a bloom filter trie. However, today this is used too rarely to -// justify that cost. -class BASE_EXPORT EventNameFilter : public TraceEventFilter { - public: - using EventNamesWhitelist = std::unordered_set; - static const char kName[]; - - EventNameFilter(std::unique_ptr); - ~EventNameFilter() override; - - // TraceEventFilter implementation. - bool FilterTraceEvent(const TraceEvent&) const override; - - private: - std::unique_ptr event_names_whitelist_; - - DISALLOW_COPY_AND_ASSIGN(EventNameFilter); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_EVENT_NAME_FILTER_H_ diff --git a/base/trace_event/event_name_filter_unittest.cc b/base/trace_event/event_name_filter_unittest.cc deleted file mode 100644 index 0bc2a4dafcf8f4c4e59047e1469aaea68548fa4c..0000000000000000000000000000000000000000 --- a/base/trace_event/event_name_filter_unittest.cc +++ /dev/null @@ -1,41 +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. - -#include "base/trace_event/event_name_filter.h" - -#include "base/memory/ptr_util.h" -#include "base/trace_event/trace_event_impl.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace trace_event { - -const TraceEvent& MakeTraceEvent(const char* name) { - static TraceEvent event; - event.Reset(); - event.Initialize(0, TimeTicks(), ThreadTicks(), 'b', nullptr, name, "", 0, 0, - 0, nullptr, nullptr, nullptr, nullptr, 0); - return event; -} - -TEST(TraceEventNameFilterTest, Whitelist) { - auto empty_whitelist = MakeUnique(); - auto filter = MakeUnique(std::move(empty_whitelist)); - - // No events should be filtered if the whitelist is empty. - EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("foo"))); - - auto whitelist = MakeUnique(); - whitelist->insert("foo"); - whitelist->insert("bar"); - filter = MakeUnique(std::move(whitelist)); - EXPECT_TRUE(filter->FilterTraceEvent(MakeTraceEvent("foo"))); - EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("fooz"))); - EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("afoo"))); - EXPECT_TRUE(filter->FilterTraceEvent(MakeTraceEvent("bar"))); - EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("foobar"))); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/heap_profiler.h b/base/trace_event/heap_profiler.h index cf5752462707d7a14aaf37271b7065f9efc9c6ba..a9cfcfde052e17b32c10ab1b6762f4339b9dbf77 100644 --- a/base/trace_event/heap_profiler.h +++ b/base/trace_event/heap_profiler.h @@ -5,6 +5,22 @@ #ifndef BASE_TRACE_EVENT_HEAP_PROFILER_H #define BASE_TRACE_EVENT_HEAP_PROFILER_H +// Replace with stub implementation. +#if 1 +#define TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION \ + trace_event_internal::HeapProfilerScopedTaskExecutionTracker + +namespace trace_event_internal { + +class HeapProfilerScopedTaskExecutionTracker { + public: + explicit HeapProfilerScopedTaskExecutionTracker(const char*) {} +}; + +} // namespace trace_event_internal + +#else + #include "base/compiler_specific.h" #include "base/trace_event/heap_profiler_allocation_context_tracker.h" @@ -86,4 +102,5 @@ class BASE_EXPORT HeapProfilerScopedIgnore { } // namespace trace_event_internal +#endif #endif // BASE_TRACE_EVENT_HEAP_PROFILER_H diff --git a/base/trace_event/heap_profiler_allocation_context.cc b/base/trace_event/heap_profiler_allocation_context.cc deleted file mode 100644 index 0f330a817ed6da977012ccb71afb975abaacc83f..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_allocation_context.cc +++ /dev/null @@ -1,88 +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. - -#include "base/trace_event/heap_profiler_allocation_context.h" - -#include - -#include "base/hash.h" -#include "base/macros.h" - -namespace base { -namespace trace_event { - -bool operator < (const StackFrame& lhs, const StackFrame& rhs) { - return lhs.value < rhs.value; -} - -bool operator == (const StackFrame& lhs, const StackFrame& rhs) { - return lhs.value == rhs.value; -} - -bool operator != (const StackFrame& lhs, const StackFrame& rhs) { - return !(lhs.value == rhs.value); -} - -Backtrace::Backtrace(): frame_count(0) {} - -bool operator==(const Backtrace& lhs, const Backtrace& rhs) { - if (lhs.frame_count != rhs.frame_count) return false; - return std::equal(lhs.frames, lhs.frames + lhs.frame_count, rhs.frames); -} - -bool operator!=(const Backtrace& lhs, const Backtrace& rhs) { - return !(lhs == rhs); -} - -AllocationContext::AllocationContext(): type_name(nullptr) {} - -AllocationContext::AllocationContext(const Backtrace& backtrace, - const char* type_name) - : backtrace(backtrace), type_name(type_name) {} - -bool operator==(const AllocationContext& lhs, const AllocationContext& rhs) { - return (lhs.backtrace == rhs.backtrace) && (lhs.type_name == rhs.type_name); -} - -bool operator!=(const AllocationContext& lhs, const AllocationContext& rhs) { - return !(lhs == rhs); -} -} // namespace trace_event -} // namespace base - -namespace BASE_HASH_NAMESPACE { -using base::trace_event::AllocationContext; -using base::trace_event::Backtrace; -using base::trace_event::StackFrame; - -size_t hash::operator()(const StackFrame& frame) const { - return hash()(frame.value); -} - -size_t hash::operator()(const Backtrace& backtrace) const { - const void* values[Backtrace::kMaxFrameCount]; - for (size_t i = 0; i != backtrace.frame_count; ++i) { - values[i] = backtrace.frames[i].value; - } - return base::SuperFastHash( - reinterpret_cast(values), - static_cast(backtrace.frame_count * sizeof(*values))); -} - -size_t hash::operator()(const AllocationContext& ctx) const { - size_t backtrace_hash = hash()(ctx.backtrace); - - // Multiplicative hash from [Knuth 1998]. Works best if |size_t| is 32 bits, - // because the magic number is a prime very close to 2^32 / golden ratio, but - // will still redistribute keys bijectively on 64-bit architectures because - // the magic number is coprime to 2^64. - size_t type_hash = reinterpret_cast(ctx.type_name) * 2654435761; - - // Multiply one side to break the commutativity of +. Multiplication with a - // number coprime to |numeric_limits::max() + 1| is bijective so - // randomness is preserved. - return (backtrace_hash * 3) + type_hash; -} - -} // BASE_HASH_NAMESPACE diff --git a/base/trace_event/heap_profiler_allocation_context.h b/base/trace_event/heap_profiler_allocation_context.h deleted file mode 100644 index 24e2dec73f1f48ff886c0eabe3d27e1604dcd48a..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_allocation_context.h +++ /dev/null @@ -1,131 +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. - -#ifndef BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_H_ -#define BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_H_ - -#include -#include - -#include "base/base_export.h" -#include "base/containers/hash_tables.h" - -namespace base { -namespace trace_event { - -// When heap profiling is enabled, tracing keeps track of the allocation -// context for each allocation intercepted. It is generated by the -// |AllocationContextTracker| which keeps stacks of context in TLS. -// The tracker is initialized lazily. - -// The backtrace in the allocation context is a snapshot of the stack. For now, -// this is the pseudo stack where frames are created by trace event macros. In -// the future, we might add the option to use the native call stack. In that -// case, |Backtrace| and |AllocationContextTracker::GetContextSnapshot| might -// have different implementations that can be selected by a compile time flag. - -// The number of stack frames stored in the backtrace is a trade off between -// memory used for tracing and accuracy. Measurements done on a prototype -// revealed that: -// -// - In 60 percent of the cases, pseudo stack depth <= 7. -// - In 87 percent of the cases, pseudo stack depth <= 9. -// - In 95 percent of the cases, pseudo stack depth <= 11. -// -// See the design doc (https://goo.gl/4s7v7b) for more details. - -// Represents (pseudo) stack frame. Used in Backtrace class below. -// -// Conceptually stack frame is identified by its value, and type is used -// mostly to properly format the value. Value is expected to be a valid -// pointer from process' address space. -struct BASE_EXPORT StackFrame { - enum class Type { - TRACE_EVENT_NAME, // const char* string - THREAD_NAME, // const char* thread name - PROGRAM_COUNTER, // as returned by stack tracing (e.g. by StackTrace) - }; - - static StackFrame FromTraceEventName(const char* name) { - return {Type::TRACE_EVENT_NAME, name}; - } - static StackFrame FromThreadName(const char* name) { - return {Type::THREAD_NAME, name}; - } - static StackFrame FromProgramCounter(const void* pc) { - return {Type::PROGRAM_COUNTER, pc}; - } - - Type type; - const void* value; -}; - -bool BASE_EXPORT operator < (const StackFrame& lhs, const StackFrame& rhs); -bool BASE_EXPORT operator == (const StackFrame& lhs, const StackFrame& rhs); -bool BASE_EXPORT operator != (const StackFrame& lhs, const StackFrame& rhs); - -struct BASE_EXPORT Backtrace { - Backtrace(); - - // If the stack is higher than what can be stored here, the bottom frames - // (the ones closer to main()) are stored. Depth of 12 is enough for most - // pseudo traces (see above), but not for native traces, where we need more. - enum { kMaxFrameCount = 48 }; - StackFrame frames[kMaxFrameCount]; - size_t frame_count; -}; - -bool BASE_EXPORT operator==(const Backtrace& lhs, const Backtrace& rhs); -bool BASE_EXPORT operator!=(const Backtrace& lhs, const Backtrace& rhs); - -// The |AllocationContext| is context metadata that is kept for every allocation -// when heap profiling is enabled. To simplify memory management for book- -// keeping, this struct has a fixed size. -struct BASE_EXPORT AllocationContext { - AllocationContext(); - AllocationContext(const Backtrace& backtrace, const char* type_name); - - Backtrace backtrace; - - // Type name of the type stored in the allocated memory. A null pointer - // indicates "unknown type". Grouping is done by comparing pointers, not by - // deep string comparison. In a component build, where a type name can have a - // string literal in several dynamic libraries, this may distort grouping. - const char* type_name; -}; - -bool BASE_EXPORT operator==(const AllocationContext& lhs, - const AllocationContext& rhs); -bool BASE_EXPORT operator!=(const AllocationContext& lhs, - const AllocationContext& rhs); - -// Struct to store the size and count of the allocations. -struct AllocationMetrics { - size_t size; - size_t count; -}; - -} // namespace trace_event -} // namespace base - -namespace BASE_HASH_NAMESPACE { - -template <> -struct BASE_EXPORT hash { - size_t operator()(const base::trace_event::StackFrame& frame) const; -}; - -template <> -struct BASE_EXPORT hash { - size_t operator()(const base::trace_event::Backtrace& backtrace) const; -}; - -template <> -struct BASE_EXPORT hash { - size_t operator()(const base::trace_event::AllocationContext& context) const; -}; - -} // BASE_HASH_NAMESPACE - -#endif // BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_H_ diff --git a/base/trace_event/heap_profiler_allocation_context_tracker.cc b/base/trace_event/heap_profiler_allocation_context_tracker.cc deleted file mode 100644 index b47dc16eddd02b2565bd18511d0490a17c488056..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_allocation_context_tracker.cc +++ /dev/null @@ -1,250 +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. - -#include "base/trace_event/heap_profiler_allocation_context_tracker.h" - -#include -#include - -#include "base/atomicops.h" -#include "base/debug/leak_annotations.h" -#include "base/threading/platform_thread.h" -#include "base/threading/thread_local_storage.h" -#include "base/trace_event/heap_profiler_allocation_context.h" - -#if defined(OS_LINUX) || defined(OS_ANDROID) -#include -#endif - -namespace base { -namespace trace_event { - -subtle::Atomic32 AllocationContextTracker::capture_mode_ = - static_cast(AllocationContextTracker::CaptureMode::DISABLED); - -namespace { - -const size_t kMaxStackDepth = 128u; -const size_t kMaxTaskDepth = 16u; -AllocationContextTracker* const kInitializingSentinel = - reinterpret_cast(-1); - -ThreadLocalStorage::StaticSlot g_tls_alloc_ctx_tracker = TLS_INITIALIZER; - -// This function is added to the TLS slot to clean up the instance when the -// thread exits. -void DestructAllocationContextTracker(void* alloc_ctx_tracker) { - delete static_cast(alloc_ctx_tracker); -} - -// Cannot call ThreadIdNameManager::GetName because it holds a lock and causes -// deadlock when lock is already held by ThreadIdNameManager before the current -// allocation. Gets the thread name from kernel if available or returns a string -// with id. This function intenionally leaks the allocated strings since they -// are used to tag allocations even after the thread dies. -const char* GetAndLeakThreadName() { - char name[16]; -#if defined(OS_LINUX) || defined(OS_ANDROID) - // If the thread name is not set, try to get it from prctl. Thread name might - // not be set in cases where the thread started before heap profiling was - // enabled. - int err = prctl(PR_GET_NAME, name); - if (!err) { - return strdup(name); - } -#endif // defined(OS_LINUX) || defined(OS_ANDROID) - - // Use tid if we don't have a thread name. - snprintf(name, sizeof(name), "%lu", - static_cast(PlatformThread::CurrentId())); - return strdup(name); -} - -} // namespace - -// static -AllocationContextTracker* -AllocationContextTracker::GetInstanceForCurrentThread() { - AllocationContextTracker* tracker = - static_cast(g_tls_alloc_ctx_tracker.Get()); - if (tracker == kInitializingSentinel) - return nullptr; // Re-entrancy case. - - if (!tracker) { - g_tls_alloc_ctx_tracker.Set(kInitializingSentinel); - tracker = new AllocationContextTracker(); - g_tls_alloc_ctx_tracker.Set(tracker); - } - - return tracker; -} - -AllocationContextTracker::AllocationContextTracker() - : thread_name_(nullptr), ignore_scope_depth_(0) { - pseudo_stack_.reserve(kMaxStackDepth); - task_contexts_.reserve(kMaxTaskDepth); -} -AllocationContextTracker::~AllocationContextTracker() {} - -// static -void AllocationContextTracker::SetCurrentThreadName(const char* name) { - if (name && capture_mode() != CaptureMode::DISABLED) { - GetInstanceForCurrentThread()->thread_name_ = name; - } -} - -// static -void AllocationContextTracker::SetCaptureMode(CaptureMode mode) { - // When enabling capturing, also initialize the TLS slot. This does not create - // a TLS instance yet. - if (mode != CaptureMode::DISABLED && !g_tls_alloc_ctx_tracker.initialized()) - g_tls_alloc_ctx_tracker.Initialize(DestructAllocationContextTracker); - - // Release ordering ensures that when a thread observes |capture_mode_| to - // be true through an acquire load, the TLS slot has been initialized. - subtle::Release_Store(&capture_mode_, static_cast(mode)); -} - -void AllocationContextTracker::PushPseudoStackFrame( - AllocationContextTracker::PseudoStackFrame stack_frame) { - // Impose a limit on the height to verify that every push is popped, because - // in practice the pseudo stack never grows higher than ~20 frames. - if (pseudo_stack_.size() < kMaxStackDepth) - pseudo_stack_.push_back(stack_frame); - else - NOTREACHED(); -} - -void AllocationContextTracker::PopPseudoStackFrame( - AllocationContextTracker::PseudoStackFrame stack_frame) { - // Guard for stack underflow. If tracing was started with a TRACE_EVENT in - // scope, the frame was never pushed, so it is possible that pop is called - // on an empty stack. - if (pseudo_stack_.empty()) - return; - - // Assert that pushes and pops are nested correctly. This DCHECK can be - // hit if some TRACE_EVENT macro is unbalanced (a TRACE_EVENT_END* call - // without a corresponding TRACE_EVENT_BEGIN). - DCHECK(stack_frame == pseudo_stack_.back()) - << "Encountered an unmatched TRACE_EVENT_END: " - << stack_frame.trace_event_name - << " vs event in stack: " << pseudo_stack_.back().trace_event_name; - - pseudo_stack_.pop_back(); -} - -void AllocationContextTracker::PushCurrentTaskContext(const char* context) { - DCHECK(context); - if (task_contexts_.size() < kMaxTaskDepth) - task_contexts_.push_back(context); - else - NOTREACHED(); -} - -void AllocationContextTracker::PopCurrentTaskContext(const char* context) { - // Guard for stack underflow. If tracing was started with a TRACE_EVENT in - // scope, the context was never pushed, so it is possible that pop is called - // on an empty stack. - if (task_contexts_.empty()) - return; - - DCHECK_EQ(context, task_contexts_.back()) - << "Encountered an unmatched context end"; - task_contexts_.pop_back(); -} - -// static -bool AllocationContextTracker::GetContextSnapshot(AllocationContext* ctx) { - if (ignore_scope_depth_) - return false; - - CaptureMode mode = static_cast( - subtle::NoBarrier_Load(&capture_mode_)); - - auto* backtrace = std::begin(ctx->backtrace.frames); - auto* backtrace_end = std::end(ctx->backtrace.frames); - - if (!thread_name_) { - // Ignore the string allocation made by GetAndLeakThreadName to avoid - // reentrancy. - ignore_scope_depth_++; - thread_name_ = GetAndLeakThreadName(); - ANNOTATE_LEAKING_OBJECT_PTR(thread_name_); - DCHECK(thread_name_); - ignore_scope_depth_--; - } - - // Add the thread name as the first entry in pseudo stack. - if (thread_name_) { - *backtrace++ = StackFrame::FromThreadName(thread_name_); - } - - switch (mode) { - case CaptureMode::DISABLED: - { - break; - } - case CaptureMode::PSEUDO_STACK: - { - for (const PseudoStackFrame& stack_frame : pseudo_stack_) { - if (backtrace == backtrace_end) { - break; - } - *backtrace++ = - StackFrame::FromTraceEventName(stack_frame.trace_event_name); - } - break; - } - case CaptureMode::NATIVE_STACK: - { - // Backtrace contract requires us to return bottom frames, i.e. - // from main() and up. Stack unwinding produces top frames, i.e. - // from this point and up until main(). We request many frames to - // make sure we reach main(), and then copy bottom portion of them. - const void* frames[128]; - static_assert(arraysize(frames) >= Backtrace::kMaxFrameCount, - "not requesting enough frames to fill Backtrace"); -#if HAVE_TRACE_STACK_FRAME_POINTERS && !defined(OS_NACL) - size_t frame_count = debug::TraceStackFramePointers( - frames, - arraysize(frames), - 1 /* exclude this function from the trace */ ); -#else - size_t frame_count = 0; - NOTREACHED(); -#endif - - // Copy frames backwards - size_t backtrace_capacity = backtrace_end - backtrace; - int32_t top_frame_index = (backtrace_capacity >= frame_count) - ? 0 - : frame_count - backtrace_capacity; - for (int32_t i = frame_count - 1; i >= top_frame_index; --i) { - const void* frame = frames[i]; - *backtrace++ = StackFrame::FromProgramCounter(frame); - } - break; - } - } - - ctx->backtrace.frame_count = backtrace - std::begin(ctx->backtrace.frames); - - // TODO(ssid): Fix crbug.com/594803 to add file name as 3rd dimension - // (component name) in the heap profiler and not piggy back on the type name. - if (!task_contexts_.empty()) { - ctx->type_name = task_contexts_.back(); - } else if (!pseudo_stack_.empty()) { - // If task context was unavailable, then the category names are taken from - // trace events. - ctx->type_name = pseudo_stack_.back().trace_event_category; - } else { - ctx->type_name = nullptr; - } - - return true; -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/heap_profiler_allocation_context_tracker.h b/base/trace_event/heap_profiler_allocation_context_tracker.h deleted file mode 100644 index 4f2a8c95026ac611350763ea4df674f8bd60c743..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_allocation_context_tracker.h +++ /dev/null @@ -1,121 +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. - -#ifndef BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_TRACKER_H_ -#define BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_TRACKER_H_ - -#include - -#include "base/atomicops.h" -#include "base/base_export.h" -#include "base/debug/stack_trace.h" -#include "base/macros.h" -#include "base/trace_event/heap_profiler_allocation_context.h" - -namespace base { -namespace trace_event { - -// The allocation context tracker keeps track of thread-local context for heap -// profiling. It includes a pseudo stack of trace events. On every allocation -// the tracker provides a snapshot of its context in the form of an -// |AllocationContext| that is to be stored together with the allocation -// details. -class BASE_EXPORT AllocationContextTracker { - public: - enum class CaptureMode: int32_t { - DISABLED, // Don't capture anything - PSEUDO_STACK, // GetContextSnapshot() returns pseudo stack trace - NATIVE_STACK // GetContextSnapshot() returns native (real) stack trace - }; - - // Stack frame constructed from trace events in codebase. - struct BASE_EXPORT PseudoStackFrame { - const char* trace_event_category; - const char* trace_event_name; - - bool operator==(const PseudoStackFrame& other) const { - return trace_event_category == other.trace_event_category && - trace_event_name == other.trace_event_name; - } - }; - - // Globally sets capturing mode. - // TODO(primiano): How to guard against *_STACK -> DISABLED -> *_STACK? - static void SetCaptureMode(CaptureMode mode); - - // Returns global capturing mode. - inline static CaptureMode capture_mode() { - // A little lag after heap profiling is enabled or disabled is fine, it is - // more important that the check is as cheap as possible when capturing is - // not enabled, so do not issue a memory barrier in the fast path. - if (subtle::NoBarrier_Load(&capture_mode_) == - static_cast(CaptureMode::DISABLED)) - return CaptureMode::DISABLED; - - // In the slow path, an acquire load is required to pair with the release - // store in |SetCaptureMode|. This is to ensure that the TLS slot for - // the thread-local allocation context tracker has been initialized if - // |capture_mode| returns something other than DISABLED. - return static_cast(subtle::Acquire_Load(&capture_mode_)); - } - - // Returns the thread-local instance, creating one if necessary. Returns - // always a valid instance, unless it is called re-entrantly, in which case - // returns nullptr in the nested calls. - static AllocationContextTracker* GetInstanceForCurrentThread(); - - // Set the thread name in the AllocationContextTracker of the current thread - // if capture is enabled. - static void SetCurrentThreadName(const char* name); - - // Starts and ends a new ignore scope between which the allocations are - // ignored by the heap profiler. GetContextSnapshot() returns false when - // allocations are ignored. - void begin_ignore_scope() { ignore_scope_depth_++; } - void end_ignore_scope() { - if (ignore_scope_depth_) - ignore_scope_depth_--; - } - - // Pushes a frame onto the thread-local pseudo stack. - void PushPseudoStackFrame(PseudoStackFrame stack_frame); - - // Pops a frame from the thread-local pseudo stack. - void PopPseudoStackFrame(PseudoStackFrame stack_frame); - - // Push and pop current task's context. A stack is used to support nested - // tasks and the top of the stack will be used in allocation context. - void PushCurrentTaskContext(const char* context); - void PopCurrentTaskContext(const char* context); - - // Fills a snapshot of the current thread-local context. Doesn't fill and - // returns false if allocations are being ignored. - bool GetContextSnapshot(AllocationContext* snapshot); - - ~AllocationContextTracker(); - - private: - AllocationContextTracker(); - - static subtle::Atomic32 capture_mode_; - - // The pseudo stack where frames are |TRACE_EVENT| names. - std::vector pseudo_stack_; - - // The thread name is used as the first entry in the pseudo stack. - const char* thread_name_; - - // Stack of tasks' contexts. Context serves as a different dimension than - // pseudo stack to cluster allocations. - std::vector task_contexts_; - - uint32_t ignore_scope_depth_; - - DISALLOW_COPY_AND_ASSIGN(AllocationContextTracker); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_TRACKER_H_ diff --git a/base/trace_event/heap_profiler_allocation_context_tracker_unittest.cc b/base/trace_event/heap_profiler_allocation_context_tracker_unittest.cc deleted file mode 100644 index 6317886b0d7fb59c48f8f8e80471258cfce75f2a..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_allocation_context_tracker_unittest.cc +++ /dev/null @@ -1,321 +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. - -#include - -#include - -#include "base/memory/ref_counted.h" -#include "base/pending_task.h" -#include "base/trace_event/heap_profiler.h" -#include "base/trace_event/heap_profiler_allocation_context.h" -#include "base/trace_event/heap_profiler_allocation_context_tracker.h" -#include "base/trace_event/memory_dump_manager.h" -#include "base/trace_event/trace_event.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace trace_event { - -// Define all strings once, because the pseudo stack requires pointer equality, -// and string interning is unreliable. -const char kThreadName[] = "TestThread"; -const char kCupcake[] = "Cupcake"; -const char kDonut[] = "Donut"; -const char kEclair[] = "Eclair"; -const char kFroyo[] = "Froyo"; -const char kGingerbread[] = "Gingerbread"; - -const char kFilteringTraceConfig[] = - "{" - " \"event_filters\": [" - " {" - " \"excluded_categories\": []," - " \"filter_args\": {}," - " \"filter_predicate\": \"heap_profiler_predicate\"," - " \"included_categories\": [" - " \"*\"," - " \"" TRACE_DISABLED_BY_DEFAULT("Testing") "\"]" - " }" - " ]" - "}"; - -// Asserts that the fixed-size array |expected_backtrace| matches the backtrace -// in |AllocationContextTracker::GetContextSnapshot|. -template -void AssertBacktraceEquals(const StackFrame(&expected_backtrace)[N]) { - AllocationContext ctx; - ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() - ->GetContextSnapshot(&ctx)); - - auto* actual = std::begin(ctx.backtrace.frames); - auto* actual_bottom = actual + ctx.backtrace.frame_count; - auto expected = std::begin(expected_backtrace); - auto expected_bottom = std::end(expected_backtrace); - - // Note that this requires the pointers to be equal, this is not doing a deep - // string comparison. - for (; actual != actual_bottom && expected != expected_bottom; - actual++, expected++) - ASSERT_EQ(*expected, *actual); - - // Ensure that the height of the stacks is the same. - ASSERT_EQ(actual, actual_bottom); - ASSERT_EQ(expected, expected_bottom); -} - -void AssertBacktraceContainsOnlyThreadName() { - StackFrame t = StackFrame::FromThreadName(kThreadName); - AllocationContext ctx; - ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() - ->GetContextSnapshot(&ctx)); - - ASSERT_EQ(1u, ctx.backtrace.frame_count); - ASSERT_EQ(t, ctx.backtrace.frames[0]); -} - -class AllocationContextTrackerTest : public testing::Test { - public: - void SetUp() override { - AllocationContextTracker::SetCaptureMode( - AllocationContextTracker::CaptureMode::PSEUDO_STACK); - // Enabling memory-infra category sets default memory dump config which - // includes filters for capturing pseudo stack. - TraceConfig config(kFilteringTraceConfig); - TraceLog::GetInstance()->SetEnabled(config, TraceLog::FILTERING_MODE); - AllocationContextTracker::SetCurrentThreadName(kThreadName); - } - - void TearDown() override { - AllocationContextTracker::SetCaptureMode( - AllocationContextTracker::CaptureMode::DISABLED); - TraceLog::GetInstance()->SetDisabled(TraceLog::FILTERING_MODE); - } -}; - -// Check that |TRACE_EVENT| macros push and pop to the pseudo stack correctly. -TEST_F(AllocationContextTrackerTest, PseudoStackScopedTrace) { - StackFrame t = StackFrame::FromThreadName(kThreadName); - StackFrame c = StackFrame::FromTraceEventName(kCupcake); - StackFrame d = StackFrame::FromTraceEventName(kDonut); - StackFrame e = StackFrame::FromTraceEventName(kEclair); - StackFrame f = StackFrame::FromTraceEventName(kFroyo); - - AssertBacktraceContainsOnlyThreadName(); - - { - TRACE_EVENT0("Testing", kCupcake); - StackFrame frame_c[] = {t, c}; - AssertBacktraceEquals(frame_c); - - { - TRACE_EVENT0("Testing", kDonut); - StackFrame frame_cd[] = {t, c, d}; - AssertBacktraceEquals(frame_cd); - } - - AssertBacktraceEquals(frame_c); - - { - TRACE_EVENT0("Testing", kEclair); - StackFrame frame_ce[] = {t, c, e}; - AssertBacktraceEquals(frame_ce); - } - - { - TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("NotTesting"), kDonut); - TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("Testing"), kCupcake); - StackFrame frame_cc[] = {t, c, c}; - AssertBacktraceEquals(frame_cc); - } - - AssertBacktraceEquals(frame_c); - } - - AssertBacktraceContainsOnlyThreadName(); - - { - TRACE_EVENT0("Testing", kFroyo); - StackFrame frame_f[] = {t, f}; - AssertBacktraceEquals(frame_f); - } - - AssertBacktraceContainsOnlyThreadName(); -} - -// Same as |PseudoStackScopedTrace|, but now test the |TRACE_EVENT_BEGIN| and -// |TRACE_EVENT_END| macros. -TEST_F(AllocationContextTrackerTest, PseudoStackBeginEndTrace) { - StackFrame t = StackFrame::FromThreadName(kThreadName); - StackFrame c = StackFrame::FromTraceEventName(kCupcake); - StackFrame d = StackFrame::FromTraceEventName(kDonut); - StackFrame e = StackFrame::FromTraceEventName(kEclair); - StackFrame f = StackFrame::FromTraceEventName(kFroyo); - - StackFrame frame_c[] = {t, c}; - StackFrame frame_cd[] = {t, c, d}; - StackFrame frame_ce[] = {t, c, e}; - StackFrame frame_f[] = {t, f}; - - AssertBacktraceContainsOnlyThreadName(); - - TRACE_EVENT_BEGIN0("Testing", kCupcake); - AssertBacktraceEquals(frame_c); - - TRACE_EVENT_BEGIN0("Testing", kDonut); - AssertBacktraceEquals(frame_cd); - TRACE_EVENT_END0("Testing", kDonut); - - AssertBacktraceEquals(frame_c); - - TRACE_EVENT_BEGIN0("Testing", kEclair); - AssertBacktraceEquals(frame_ce); - TRACE_EVENT_END0("Testing", kEclair); - - AssertBacktraceEquals(frame_c); - TRACE_EVENT_END0("Testing", kCupcake); - - AssertBacktraceContainsOnlyThreadName(); - - TRACE_EVENT_BEGIN0("Testing", kFroyo); - AssertBacktraceEquals(frame_f); - TRACE_EVENT_END0("Testing", kFroyo); - - AssertBacktraceContainsOnlyThreadName(); -} - -TEST_F(AllocationContextTrackerTest, PseudoStackMixedTrace) { - StackFrame t = StackFrame::FromThreadName(kThreadName); - StackFrame c = StackFrame::FromTraceEventName(kCupcake); - StackFrame d = StackFrame::FromTraceEventName(kDonut); - StackFrame e = StackFrame::FromTraceEventName(kEclair); - StackFrame f = StackFrame::FromTraceEventName(kFroyo); - - StackFrame frame_c[] = {t, c}; - StackFrame frame_cd[] = {t, c, d}; - StackFrame frame_e[] = {t, e}; - StackFrame frame_ef[] = {t, e, f}; - - AssertBacktraceContainsOnlyThreadName(); - - TRACE_EVENT_BEGIN0("Testing", kCupcake); - AssertBacktraceEquals(frame_c); - - { - TRACE_EVENT0("Testing", kDonut); - AssertBacktraceEquals(frame_cd); - } - - AssertBacktraceEquals(frame_c); - TRACE_EVENT_END0("Testing", kCupcake); - AssertBacktraceContainsOnlyThreadName(); - - { - TRACE_EVENT0("Testing", kEclair); - AssertBacktraceEquals(frame_e); - - TRACE_EVENT_BEGIN0("Testing", kFroyo); - AssertBacktraceEquals(frame_ef); - TRACE_EVENT_END0("Testing", kFroyo); - AssertBacktraceEquals(frame_e); - } - - AssertBacktraceContainsOnlyThreadName(); -} - -TEST_F(AllocationContextTrackerTest, BacktraceTakesTop) { - StackFrame t = StackFrame::FromThreadName(kThreadName); - StackFrame c = StackFrame::FromTraceEventName(kCupcake); - StackFrame f = StackFrame::FromTraceEventName(kFroyo); - - // Push 11 events onto the pseudo stack. - TRACE_EVENT0("Testing", kCupcake); - TRACE_EVENT0("Testing", kCupcake); - TRACE_EVENT0("Testing", kCupcake); - - TRACE_EVENT0("Testing", kCupcake); - TRACE_EVENT0("Testing", kCupcake); - TRACE_EVENT0("Testing", kCupcake); - TRACE_EVENT0("Testing", kCupcake); - - TRACE_EVENT0("Testing", kCupcake); - TRACE_EVENT0("Testing", kDonut); - TRACE_EVENT0("Testing", kEclair); - TRACE_EVENT0("Testing", kFroyo); - - { - TRACE_EVENT0("Testing", kGingerbread); - AllocationContext ctx; - ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() - ->GetContextSnapshot(&ctx)); - - // The pseudo stack relies on pointer equality, not deep string comparisons. - ASSERT_EQ(t, ctx.backtrace.frames[0]); - ASSERT_EQ(c, ctx.backtrace.frames[1]); - ASSERT_EQ(f, ctx.backtrace.frames[11]); - } - - { - AllocationContext ctx; - ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() - ->GetContextSnapshot(&ctx)); - ASSERT_EQ(t, ctx.backtrace.frames[0]); - ASSERT_EQ(c, ctx.backtrace.frames[1]); - ASSERT_EQ(f, ctx.backtrace.frames[11]); - } -} - -TEST_F(AllocationContextTrackerTest, TrackCategoryName) { - const char kContext1[] = "context1"; - const char kContext2[] = "context2"; - { - // The context from the scoped task event should be used as type name. - TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event1(kContext1); - AllocationContext ctx1; - ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() - ->GetContextSnapshot(&ctx1)); - ASSERT_EQ(kContext1, ctx1.type_name); - - // In case of nested events, the last event's context should be used. - TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event2(kContext2); - AllocationContext ctx2; - ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() - ->GetContextSnapshot(&ctx2)); - ASSERT_EQ(kContext2, ctx2.type_name); - } - - { - // Type should be category name of the last seen trace event. - TRACE_EVENT0("Testing", kCupcake); - AllocationContext ctx1; - ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() - ->GetContextSnapshot(&ctx1)); - ASSERT_EQ("Testing", std::string(ctx1.type_name)); - - TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("Testing"), kCupcake); - AllocationContext ctx2; - ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() - ->GetContextSnapshot(&ctx2)); - ASSERT_EQ(TRACE_DISABLED_BY_DEFAULT("Testing"), - std::string(ctx2.type_name)); - } - - // Type should be nullptr without task event. - AllocationContext ctx; - ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() - ->GetContextSnapshot(&ctx)); - ASSERT_FALSE(ctx.type_name); -} - -TEST_F(AllocationContextTrackerTest, IgnoreAllocationTest) { - TRACE_EVENT0("Testing", kCupcake); - TRACE_EVENT0("Testing", kDonut); - HEAP_PROFILER_SCOPED_IGNORE; - AllocationContext ctx; - ASSERT_FALSE(AllocationContextTracker::GetInstanceForCurrentThread() - ->GetContextSnapshot(&ctx)); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/heap_profiler_allocation_register.cc b/base/trace_event/heap_profiler_allocation_register.cc deleted file mode 100644 index b9f440adb69d5a131fdc14fa2cde7a2f1da6466d..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_allocation_register.cc +++ /dev/null @@ -1,194 +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. - -#include "base/trace_event/heap_profiler_allocation_register.h" - -#include -#include - -#include "base/trace_event/trace_event_memory_overhead.h" - -namespace base { -namespace trace_event { - -AllocationRegister::ConstIterator::ConstIterator( - const AllocationRegister& alloc_register, - AllocationIndex index) - : register_(alloc_register), index_(index) {} - -void AllocationRegister::ConstIterator::operator++() { - index_ = register_.allocations_.Next(index_ + 1); -} - -bool AllocationRegister::ConstIterator::operator!=( - const ConstIterator& other) const { - return index_ != other.index_; -} - -AllocationRegister::Allocation AllocationRegister::ConstIterator::operator*() - const { - return register_.GetAllocation(index_); -} - -size_t AllocationRegister::BacktraceHasher::operator()( - const Backtrace& backtrace) const { - const size_t kSampleLength = 10; - - uintptr_t total_value = 0; - - size_t head_end = std::min(backtrace.frame_count, kSampleLength); - for (size_t i = 0; i != head_end; ++i) { - total_value += reinterpret_cast(backtrace.frames[i].value); - } - - size_t tail_start = backtrace.frame_count - - std::min(backtrace.frame_count - head_end, kSampleLength); - for (size_t i = tail_start; i != backtrace.frame_count; ++i) { - total_value += reinterpret_cast(backtrace.frames[i].value); - } - - total_value += backtrace.frame_count; - - // These magic constants give best results in terms of average collisions - // per backtrace. They were found by replaying real backtraces from Linux - // and Android against different hash functions. - return (total_value * 131101) >> 14; -} - -size_t AllocationRegister::AddressHasher::operator()( - const void* address) const { - // The multiplicative hashing scheme from [Knuth 1998]. The value of |a| has - // been chosen carefully based on measurements with real-word data (addresses - // recorded from a Chrome trace run). It is the first prime after 2^17. For - // |shift|, 15 yield good results for both 2^18 and 2^19 bucket sizes. - // Microbenchmarks show that this simple scheme outperforms fancy hashes like - // Murmur3 by 20 to 40 percent. - const uintptr_t key = reinterpret_cast(address); - const uintptr_t a = 131101; - const uintptr_t shift = 15; - const uintptr_t h = (key * a) >> shift; - return h; -} - -AllocationRegister::AllocationRegister() - : AllocationRegister(kAllocationCapacity, kBacktraceCapacity) {} - -AllocationRegister::AllocationRegister(size_t allocation_capacity, - size_t backtrace_capacity) - : allocations_(allocation_capacity), backtraces_(backtrace_capacity) { - Backtrace sentinel = {}; - sentinel.frames[0] = StackFrame::FromThreadName("[out of heap profiler mem]"); - sentinel.frame_count = 1; - - // Rationale for max / 2: in theory we could just start the sentinel with a - // refcount == 0. However, using max / 2 allows short circuiting of the - // conditional in RemoveBacktrace() keeping the sentinel logic out of the fast - // path. From a functional viewpoint, the sentinel is safe even if we wrap - // over refcount because . - BacktraceMap::KVPair::second_type sentinel_refcount = - std::numeric_limits::max() / 2; - auto index_and_flag = backtraces_.Insert(sentinel, sentinel_refcount); - DCHECK(index_and_flag.second); - DCHECK_EQ(index_and_flag.first, kOutOfStorageBacktraceIndex); -} - -AllocationRegister::~AllocationRegister() {} - -bool AllocationRegister::Insert(const void* address, - size_t size, - const AllocationContext& context) { - DCHECK(address != nullptr); - if (size == 0) { - return false; - } - - AllocationInfo info = {size, context.type_name, - InsertBacktrace(context.backtrace)}; - - // Try to insert the allocation. - auto index_and_flag = allocations_.Insert(address, info); - if (!index_and_flag.second && - index_and_flag.first != AllocationMap::kInvalidKVIndex) { - // |address| is already there - overwrite the allocation info. - auto& old_info = allocations_.Get(index_and_flag.first).second; - RemoveBacktrace(old_info.backtrace_index); - old_info = info; - return true; - } - - return index_and_flag.second; -} - -void AllocationRegister::Remove(const void* address) { - auto index = allocations_.Find(address); - if (index == AllocationMap::kInvalidKVIndex) { - return; - } - - const AllocationInfo& info = allocations_.Get(index).second; - RemoveBacktrace(info.backtrace_index); - allocations_.Remove(index); -} - -bool AllocationRegister::Get(const void* address, - Allocation* out_allocation) const { - auto index = allocations_.Find(address); - if (index == AllocationMap::kInvalidKVIndex) { - return false; - } - - if (out_allocation) { - *out_allocation = GetAllocation(index); - } - return true; -} - -AllocationRegister::ConstIterator AllocationRegister::begin() const { - return ConstIterator(*this, allocations_.Next(0)); -} - -AllocationRegister::ConstIterator AllocationRegister::end() const { - return ConstIterator(*this, AllocationMap::kInvalidKVIndex); -} - -void AllocationRegister::EstimateTraceMemoryOverhead( - TraceEventMemoryOverhead* overhead) const { - size_t allocated = sizeof(AllocationRegister); - size_t resident = sizeof(AllocationRegister) + - allocations_.EstimateUsedMemory() + - backtraces_.EstimateUsedMemory(); - overhead->Add("AllocationRegister", allocated, resident); -} - -AllocationRegister::BacktraceMap::KVIndex AllocationRegister::InsertBacktrace( - const Backtrace& backtrace) { - auto index = backtraces_.Insert(backtrace, 0).first; - if (index == BacktraceMap::kInvalidKVIndex) - return kOutOfStorageBacktraceIndex; - auto& backtrace_and_count = backtraces_.Get(index); - backtrace_and_count.second++; - return index; -} - -void AllocationRegister::RemoveBacktrace(BacktraceMap::KVIndex index) { - auto& backtrace_and_count = backtraces_.Get(index); - if (--backtrace_and_count.second == 0 && - index != kOutOfStorageBacktraceIndex) { - // Backtrace is not referenced anymore - remove it. - backtraces_.Remove(index); - } -} - -AllocationRegister::Allocation AllocationRegister::GetAllocation( - AllocationMap::KVIndex index) const { - const auto& address_and_info = allocations_.Get(index); - const auto& backtrace_and_count = - backtraces_.Get(address_and_info.second.backtrace_index); - return {address_and_info.first, address_and_info.second.size, - AllocationContext(backtrace_and_count.first, - address_and_info.second.type_name)}; -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/heap_profiler_allocation_register.h b/base/trace_event/heap_profiler_allocation_register.h deleted file mode 100644 index ac9872f001c218e7e82262d3522ce6c012a2770f..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_allocation_register.h +++ /dev/null @@ -1,385 +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. - -#ifndef BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_REGISTER_H_ -#define BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_REGISTER_H_ - -#include -#include - -#include - -#include "base/bits.h" -#include "base/logging.h" -#include "base/macros.h" -#include "base/process/process_metrics.h" -#include "base/template_util.h" -#include "base/trace_event/heap_profiler_allocation_context.h" -#include "build/build_config.h" - -namespace base { -namespace trace_event { - -class AllocationRegisterTest; - -namespace internal { - -// Allocates a region of virtual address space of |size| rounded up to the -// system page size. The memory is zeroed by the system. A guard page is -// added after the end. -void* AllocateGuardedVirtualMemory(size_t size); - -// Frees a region of virtual address space allocated by a call to -// |AllocateVirtualMemory|. -void FreeGuardedVirtualMemory(void* address, size_t allocated_size); - -// Hash map that mmaps memory only once in the constructor. Its API is -// similar to std::unordered_map, only index (KVIndex) is used to address -template -class FixedHashMap { - // To keep things simple we don't call destructors. - static_assert(is_trivially_destructible::value && - is_trivially_destructible::value, - "Key and Value shouldn't have destructors"); - public: - using KVPair = std::pair; - - // For implementation simplicity API uses integer index instead - // of iterators. Most operations (except Find) on KVIndex are O(1). - using KVIndex = size_t; - enum : KVIndex { kInvalidKVIndex = static_cast(-1) }; - - // Capacity controls how many items this hash map can hold, and largely - // affects memory footprint. - explicit FixedHashMap(size_t capacity) - : num_cells_(capacity), - num_inserts_dropped_(0), - cells_(static_cast( - AllocateGuardedVirtualMemory(num_cells_ * sizeof(Cell)))), - buckets_(static_cast( - AllocateGuardedVirtualMemory(NumBuckets * sizeof(Bucket)))), - free_list_(nullptr), - next_unused_cell_(0) {} - - ~FixedHashMap() { - FreeGuardedVirtualMemory(cells_, num_cells_ * sizeof(Cell)); - FreeGuardedVirtualMemory(buckets_, NumBuckets * sizeof(Bucket)); - } - - // Returns {kInvalidKVIndex, false} if the table is full. - std::pair Insert(const Key& key, const Value& value) { - Cell** p_cell = Lookup(key); - Cell* cell = *p_cell; - if (cell) { - return {static_cast(cell - cells_), false}; // not inserted - } - - // Get a free cell and link it. - cell = GetFreeCell(); - if (!cell) { - if (num_inserts_dropped_ < - std::numeric_limits::max()) { - ++num_inserts_dropped_; - } - return {kInvalidKVIndex, false}; - } - *p_cell = cell; - cell->p_prev = p_cell; - cell->next = nullptr; - - // Initialize key/value pair. Since key is 'const Key' this is the - // only way to initialize it. - new (&cell->kv) KVPair(key, value); - - return {static_cast(cell - cells_), true}; // inserted - } - - void Remove(KVIndex index) { - DCHECK_LT(index, next_unused_cell_); - - Cell* cell = &cells_[index]; - - // Unlink the cell. - *cell->p_prev = cell->next; - if (cell->next) { - cell->next->p_prev = cell->p_prev; - } - cell->p_prev = nullptr; // mark as free - - // Add it to the free list. - cell->next = free_list_; - free_list_ = cell; - } - - KVIndex Find(const Key& key) const { - Cell* cell = *Lookup(key); - return cell ? static_cast(cell - cells_) : kInvalidKVIndex; - } - - KVPair& Get(KVIndex index) { - return cells_[index].kv; - } - - const KVPair& Get(KVIndex index) const { - return cells_[index].kv; - } - - // Finds next index that has a KVPair associated with it. Search starts - // with the specified index. Returns kInvalidKVIndex if nothing was found. - // To find the first valid index, call this function with 0. Continue - // calling with the last_index + 1 until kInvalidKVIndex is returned. - KVIndex Next(KVIndex index) const { - for (;index < next_unused_cell_; ++index) { - if (cells_[index].p_prev) { - return index; - } - } - return kInvalidKVIndex; - } - - // Estimates number of bytes used in allocated memory regions. - size_t EstimateUsedMemory() const { - size_t page_size = base::GetPageSize(); - // |next_unused_cell_| is the first cell that wasn't touched, i.e. - // it's the number of touched cells. - return bits::Align(sizeof(Cell) * next_unused_cell_, page_size) + - bits::Align(sizeof(Bucket) * NumBuckets, page_size); - } - - size_t num_inserts_dropped() const { return num_inserts_dropped_; } - - private: - friend base::trace_event::AllocationRegisterTest; - - struct Cell { - KVPair kv; - Cell* next; - - // Conceptually this is |prev| in a doubly linked list. However, buckets - // also participate in the bucket's cell list - they point to the list's - // head and also need to be linked / unlinked properly. To treat these two - // cases uniformly, instead of |prev| we're storing "pointer to a Cell* - // that points to this Cell" kind of thing. So |p_prev| points to a bucket - // for the first cell in a list, and points to |next| of the previous cell - // for any other cell. With that Lookup() is the only function that handles - // buckets / cells differently. - // If |p_prev| is nullptr, the cell is in the free list. - Cell** p_prev; - }; - - using Bucket = Cell*; - - // Returns a pointer to the cell that contains or should contain the entry - // for |key|. The pointer may point at an element of |buckets_| or at the - // |next| member of an element of |cells_|. - Cell** Lookup(const Key& key) const { - // The list head is in |buckets_| at the hash offset. - Cell** p_cell = &buckets_[Hash(key)]; - - // Chase down the list until the cell that holds |key| is found, - // or until the list ends. - while (*p_cell && (*p_cell)->kv.first != key) { - p_cell = &(*p_cell)->next; - } - - return p_cell; - } - - // Returns a cell that is not being used to store an entry (either by - // recycling from the free list or by taking a fresh cell). May return - // nullptr if the hash table has run out of memory. - Cell* GetFreeCell() { - // First try to re-use a cell from the free list. - if (free_list_) { - Cell* cell = free_list_; - free_list_ = cell->next; - return cell; - } - - // If the hash table has too little capacity (when too little address space - // was reserved for |cells_|), return nullptr. - if (next_unused_cell_ >= num_cells_) { - return nullptr; - } - - // Otherwise pick the next cell that has not been touched before. - return &cells_[next_unused_cell_++]; - } - - // Returns a value in the range [0, NumBuckets - 1] (inclusive). - size_t Hash(const Key& key) const { - if (NumBuckets == (NumBuckets & ~(NumBuckets - 1))) { - // NumBuckets is a power of 2. - return KeyHasher()(key) & (NumBuckets - 1); - } else { - return KeyHasher()(key) % NumBuckets; - } - } - - // Number of cells. - size_t const num_cells_; - - // Number of calls to Insert() that were lost because the hashtable was full. - size_t num_inserts_dropped_; - - // The array of cells. This array is backed by mmapped memory. Lower indices - // are accessed first, higher indices are accessed only when the |free_list_| - // is empty. This is to minimize the amount of resident memory used. - Cell* const cells_; - - // The array of buckets (pointers into |cells_|). |buckets_[Hash(key)]| will - // contain the pointer to the linked list of cells for |Hash(key)|. - // This array is backed by mmapped memory. - mutable Bucket* buckets_; - - // The head of the free list. - Cell* free_list_; - - // The index of the first element of |cells_| that has not been used before. - // If the free list is empty and a new cell is needed, the cell at this index - // is used. This is the high water mark for the number of entries stored. - size_t next_unused_cell_; - - DISALLOW_COPY_AND_ASSIGN(FixedHashMap); -}; - -} // namespace internal - -class TraceEventMemoryOverhead; - -// The allocation register keeps track of all allocations that have not been -// freed. Internally it has two hashtables: one for Backtraces and one for -// actual allocations. Sizes of both hashtables are fixed, and this class -// allocates (mmaps) only in its constructor. -// -// When either hash table hits max size, new inserts are dropped. -class BASE_EXPORT AllocationRegister { - public: - // Details about an allocation. - struct Allocation { - const void* address; - size_t size; - AllocationContext context; - }; - - // An iterator that iterates entries in no particular order. - class BASE_EXPORT ConstIterator { - public: - void operator++(); - bool operator!=(const ConstIterator& other) const; - Allocation operator*() const; - - private: - friend class AllocationRegister; - using AllocationIndex = size_t; - - ConstIterator(const AllocationRegister& alloc_register, - AllocationIndex index); - - const AllocationRegister& register_; - AllocationIndex index_; - }; - - AllocationRegister(); - AllocationRegister(size_t allocation_capacity, size_t backtrace_capacity); - - ~AllocationRegister(); - - // Inserts allocation details into the table. If the address was present - // already, its details are updated. |address| must not be null. - // - // Returns true if an insert occurred. Inserts may fail because the table - // is full. - bool Insert(const void* address, - size_t size, - const AllocationContext& context); - - // Removes the address from the table if it is present. It is ok to call this - // with a null pointer. - void Remove(const void* address); - - // Finds allocation for the address and fills |out_allocation|. - bool Get(const void* address, Allocation* out_allocation) const; - - ConstIterator begin() const; - ConstIterator end() const; - - // Estimates memory overhead including |sizeof(AllocationRegister)|. - void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead) const; - - private: - friend AllocationRegisterTest; - -// Expect lower number of allocations from mobile platforms. Load factor -// (capacity / bucket count) is kept less than 10 for optimal hashing. The -// number of buckets should be changed together with AddressHasher. -#if defined(OS_ANDROID) || defined(OS_IOS) - static const size_t kAllocationBuckets = 1 << 18; - static const size_t kAllocationCapacity = 1500000; -#else - static const size_t kAllocationBuckets = 1 << 19; - static const size_t kAllocationCapacity = 5000000; -#endif - - // 2^16 works well with BacktraceHasher. When increasing this number make - // sure BacktraceHasher still produces low number of collisions. - static const size_t kBacktraceBuckets = 1 << 16; -#if defined(OS_ANDROID) - static const size_t kBacktraceCapacity = 32000; // 22K was observed -#else - static const size_t kBacktraceCapacity = 55000; // 45K was observed on Linux -#endif - - struct BacktraceHasher { - size_t operator () (const Backtrace& backtrace) const; - }; - - using BacktraceMap = internal::FixedHashMap< - kBacktraceBuckets, - Backtrace, - size_t, // Number of references to the backtrace (the key). Incremented - // when an allocation that references the backtrace is inserted, - // and decremented when the allocation is removed. When the - // number drops to zero, the backtrace is removed from the map. - BacktraceHasher>; - - struct AllocationInfo { - size_t size; - const char* type_name; - BacktraceMap::KVIndex backtrace_index; - }; - - struct AddressHasher { - size_t operator () (const void* address) const; - }; - - using AllocationMap = internal::FixedHashMap< - kAllocationBuckets, - const void*, - AllocationInfo, - AddressHasher>; - - BacktraceMap::KVIndex InsertBacktrace(const Backtrace& backtrace); - void RemoveBacktrace(BacktraceMap::KVIndex index); - - Allocation GetAllocation(AllocationMap::KVIndex) const; - - AllocationMap allocations_; - BacktraceMap backtraces_; - - // Sentinel used when the |backtraces_| table is full. - // - // This is a slightly abstraction to allow for constant propagation. It - // knows that the sentinel will be the first item inserted into the table - // and that the first index retuned will be 0. The constructor DCHECKs - // this assumption. - enum : BacktraceMap::KVIndex { kOutOfStorageBacktraceIndex = 0 }; - - DISALLOW_COPY_AND_ASSIGN(AllocationRegister); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_REGISTER_H_ diff --git a/base/trace_event/heap_profiler_allocation_register_posix.cc b/base/trace_event/heap_profiler_allocation_register_posix.cc deleted file mode 100644 index 94eeb4df88ad76953b30a00d5cd8d25a9241e1e8..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_allocation_register_posix.cc +++ /dev/null @@ -1,58 +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. - -#include "base/trace_event/heap_profiler_allocation_register.h" - -#include -#include -#include - -#include "base/bits.h" -#include "base/logging.h" -#include "base/process/process_metrics.h" - -#ifndef MAP_ANONYMOUS -#define MAP_ANONYMOUS MAP_ANON -#endif - -namespace base { -namespace trace_event { -namespace internal { - -namespace { -size_t GetGuardSize() { - return GetPageSize(); -} -} - -void* AllocateGuardedVirtualMemory(size_t size) { - size = bits::Align(size, GetPageSize()); - - // Add space for a guard page at the end. - size_t map_size = size + GetGuardSize(); - - void* addr = mmap(nullptr, map_size, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - - PCHECK(addr != MAP_FAILED); - - // Mark the last page of the allocated address space as inaccessible - // (PROT_NONE). The read/write accessible space is still at least |min_size| - // bytes. - void* guard_addr = - reinterpret_cast(reinterpret_cast(addr) + size); - int result = mprotect(guard_addr, GetGuardSize(), PROT_NONE); - PCHECK(result == 0); - - return addr; -} - -void FreeGuardedVirtualMemory(void* address, size_t allocated_size) { - size_t size = bits::Align(allocated_size, GetPageSize()) + GetGuardSize(); - munmap(address, size); -} - -} // namespace internal -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/heap_profiler_event_filter.cc b/base/trace_event/heap_profiler_event_filter.cc deleted file mode 100644 index 6c91c91b136e2fe9720e31ee6a62bda5c19417e1..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_event_filter.cc +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2016 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. - -#include "base/trace_event/heap_profiler_event_filter.h" - -#include "base/trace_event/category_registry.h" -#include "base/trace_event/heap_profiler_allocation_context_tracker.h" -#include "base/trace_event/trace_category.h" -#include "base/trace_event/trace_event.h" -#include "base/trace_event/trace_event_impl.h" - -namespace base { -namespace trace_event { - -namespace { - -inline bool IsPseudoStackEnabled() { - return AllocationContextTracker::capture_mode() == - AllocationContextTracker::CaptureMode::PSEUDO_STACK; -} - -inline AllocationContextTracker* GetThreadLocalTracker() { - return AllocationContextTracker::GetInstanceForCurrentThread(); -} - -} // namespace - -// static -const char HeapProfilerEventFilter::kName[] = "heap_profiler_predicate"; - -HeapProfilerEventFilter::HeapProfilerEventFilter() {} -HeapProfilerEventFilter::~HeapProfilerEventFilter() {} - -bool HeapProfilerEventFilter::FilterTraceEvent( - const TraceEvent& trace_event) const { - if (!IsPseudoStackEnabled()) - return true; - - // TODO(primiano): Add support for events with copied name crbug.com/581079. - if (trace_event.flags() & TRACE_EVENT_FLAG_COPY) - return true; - - const auto* category = CategoryRegistry::GetCategoryByStatePtr( - trace_event.category_group_enabled()); - AllocationContextTracker::PseudoStackFrame frame = {category->name(), - trace_event.name()}; - if (trace_event.phase() == TRACE_EVENT_PHASE_BEGIN || - trace_event.phase() == TRACE_EVENT_PHASE_COMPLETE) { - GetThreadLocalTracker()->PushPseudoStackFrame(frame); - } else if (trace_event.phase() == TRACE_EVENT_PHASE_END) { - // The pop for |TRACE_EVENT_PHASE_COMPLETE| events is in |EndEvent|. - GetThreadLocalTracker()->PopPseudoStackFrame(frame); - } - // Do not filter-out any events and always return true. TraceLog adds the - // event only if it is enabled for recording. - return true; -} - -void HeapProfilerEventFilter::EndEvent(const char* category_name, - const char* event_name) const { - if (IsPseudoStackEnabled()) - GetThreadLocalTracker()->PopPseudoStackFrame({category_name, event_name}); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/heap_profiler_event_filter.h b/base/trace_event/heap_profiler_event_filter.h deleted file mode 100644 index 47368a1b0702da38c03a871017bf818b6c1bf93f..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_event_filter.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2016 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 BASE_TRACE_EVENT_HEAP_PROFILER_EVENT_FILTER_H_ -#define BASE_TRACE_EVENT_HEAP_PROFILER_EVENT_FILTER_H_ - -#include "base/base_export.h" -#include "base/macros.h" -#include "base/trace_event/trace_event_filter.h" - -namespace base { -namespace trace_event { - -class TraceEvent; - -// This filter unconditionally accepts all events and pushes/pops them from the -// thread-local AllocationContextTracker instance as they are seen. -// This is used to cheaply construct the heap profiler pseudo stack without -// having to actually record all events. -class BASE_EXPORT HeapProfilerEventFilter : public TraceEventFilter { - public: - static const char kName[]; - - HeapProfilerEventFilter(); - ~HeapProfilerEventFilter() override; - - // TraceEventFilter implementation. - bool FilterTraceEvent(const TraceEvent& trace_event) const override; - void EndEvent(const char* category_name, - const char* event_name) const override; - - private: - DISALLOW_COPY_AND_ASSIGN(HeapProfilerEventFilter); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_HEAP_PROFILER_EVENT_FILTER_H_ diff --git a/base/trace_event/heap_profiler_heap_dump_writer.cc b/base/trace_event/heap_profiler_heap_dump_writer.cc deleted file mode 100644 index 8043fff995458bb61981967a4c151c1268605453..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_heap_dump_writer.cc +++ /dev/null @@ -1,322 +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. - -#include "base/trace_event/heap_profiler_heap_dump_writer.h" - -#include - -#include -#include -#include -#include -#include - -#include "base/format_macros.h" -#include "base/logging.h" -#include "base/macros.h" -#include "base/strings/stringprintf.h" -#include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" -#include "base/trace_event/heap_profiler_type_name_deduplicator.h" -#include "base/trace_event/memory_dump_session_state.h" -#include "base/trace_event/trace_config.h" -#include "base/trace_event/trace_event_argument.h" -#include "base/trace_event/trace_log.h" - -// Most of what the |HeapDumpWriter| does is aggregating detailed information -// about the heap and deciding what to dump. The Input to this process is a list -// of |AllocationContext|s and size pairs. -// -// The pairs are grouped into |Bucket|s. A bucket is a group of (context, size) -// pairs where the properties of the contexts share a prefix. (Type name is -// considered a list of length one here.) First all pairs are put into one -// bucket that represents the entire heap. Then this bucket is recursively -// broken down into smaller buckets. Each bucket keeps track of whether further -// breakdown is possible. - -namespace base { -namespace trace_event { -namespace internal { -namespace { - -// Denotes a property of |AllocationContext| to break down by. -enum class BreakDownMode { kByBacktrace, kByTypeName }; - -// A group of bytes for which the context shares a prefix. -struct Bucket { - Bucket() - : size(0), - count(0), - backtrace_cursor(0), - is_broken_down_by_type_name(false) {} - - std::vector> - metrics_by_context; - - // The sum of the sizes of |metrics_by_context|. - size_t size; - - // The sum of number of allocations of |metrics_by_context|. - size_t count; - - // The index of the stack frame that has not yet been broken down by. For all - // elements in this bucket, the stack frames 0 up to (but not including) the - // cursor, must be equal. - size_t backtrace_cursor; - - // When true, the type name for all elements in this bucket must be equal. - bool is_broken_down_by_type_name; -}; - -// Comparison operator to order buckets by their size. -bool operator<(const Bucket& lhs, const Bucket& rhs) { - return lhs.size < rhs.size; -} - -// Groups the allocations in the bucket by |break_by|. The buckets in the -// returned list will have |backtrace_cursor| advanced or -// |is_broken_down_by_type_name| set depending on the property to group by. -std::vector GetSubbuckets(const Bucket& bucket, - BreakDownMode break_by) { - base::hash_map breakdown; - - - if (break_by == BreakDownMode::kByBacktrace) { - for (const auto& context_and_metrics : bucket.metrics_by_context) { - const Backtrace& backtrace = context_and_metrics.first->backtrace; - const StackFrame* begin = std::begin(backtrace.frames); - const StackFrame* end = begin + backtrace.frame_count; - const StackFrame* cursor = begin + bucket.backtrace_cursor; - - DCHECK_LE(cursor, end); - - if (cursor != end) { - Bucket& subbucket = breakdown[cursor->value]; - subbucket.size += context_and_metrics.second.size; - subbucket.count += context_and_metrics.second.count; - subbucket.metrics_by_context.push_back(context_and_metrics); - subbucket.backtrace_cursor = bucket.backtrace_cursor + 1; - subbucket.is_broken_down_by_type_name = - bucket.is_broken_down_by_type_name; - DCHECK_GT(subbucket.size, 0u); - DCHECK_GT(subbucket.count, 0u); - } - } - } else if (break_by == BreakDownMode::kByTypeName) { - if (!bucket.is_broken_down_by_type_name) { - for (const auto& context_and_metrics : bucket.metrics_by_context) { - const AllocationContext* context = context_and_metrics.first; - Bucket& subbucket = breakdown[context->type_name]; - subbucket.size += context_and_metrics.second.size; - subbucket.count += context_and_metrics.second.count; - subbucket.metrics_by_context.push_back(context_and_metrics); - subbucket.backtrace_cursor = bucket.backtrace_cursor; - subbucket.is_broken_down_by_type_name = true; - DCHECK_GT(subbucket.size, 0u); - DCHECK_GT(subbucket.count, 0u); - } - } - } - - std::vector buckets; - buckets.reserve(breakdown.size()); - for (auto key_bucket : breakdown) - buckets.push_back(key_bucket.second); - - return buckets; -} - -// Breaks down the bucket by |break_by|. Returns only buckets that contribute -// more than |min_size_bytes| to the total size. The long tail is omitted. -std::vector BreakDownBy(const Bucket& bucket, - BreakDownMode break_by, - size_t min_size_bytes) { - std::vector buckets = GetSubbuckets(bucket, break_by); - - // Ensure that |buckets| is a max-heap (the data structure, not memory heap), - // so its front contains the largest bucket. Buckets should be iterated - // ordered by size, but sorting the vector is overkill because the long tail - // of small buckets will be discarded. By using a max-heap, the optimal case - // where all but the first bucket are discarded is O(n). The worst case where - // no bucket is discarded is doing a heap sort, which is O(n log n). - std::make_heap(buckets.begin(), buckets.end()); - - // Keep including buckets until adding one would increase the number of - // bytes accounted for by |min_size_bytes|. The large buckets end up in - // [it, end()), [begin(), it) is the part that contains the max-heap - // of small buckets. - std::vector::iterator it; - for (it = buckets.end(); it != buckets.begin(); --it) { - if (buckets.front().size < min_size_bytes) - break; - - // Put the largest bucket in [begin, it) at |it - 1| and max-heapify - // [begin, it - 1). This puts the next largest bucket at |buckets.front()|. - std::pop_heap(buckets.begin(), it); - } - - // At this point, |buckets| looks like this (numbers are bucket sizes): - // - // <-- max-heap of small buckets ---> - // <-- large buckets by ascending size --> - // [ 19 | 11 | 13 | 7 | 2 | 5 | ... | 83 | 89 | 97 ] - // ^ ^ ^ - // | | | - // begin() it end() - - // Discard the long tail of buckets that contribute less than a percent. - buckets.erase(buckets.begin(), it); - - return buckets; -} - -} // namespace - -bool operator<(Entry lhs, Entry rhs) { - // There is no need to compare |size|. If the backtrace and type name are - // equal then the sizes must be equal as well. - return std::tie(lhs.stack_frame_id, lhs.type_id) < - std::tie(rhs.stack_frame_id, rhs.type_id); -} - -HeapDumpWriter::HeapDumpWriter(StackFrameDeduplicator* stack_frame_deduplicator, - TypeNameDeduplicator* type_name_deduplicator, - uint32_t breakdown_threshold_bytes) - : stack_frame_deduplicator_(stack_frame_deduplicator), - type_name_deduplicator_(type_name_deduplicator), - breakdown_threshold_bytes_(breakdown_threshold_bytes) { -} - -HeapDumpWriter::~HeapDumpWriter() {} - -bool HeapDumpWriter::AddEntryForBucket(const Bucket& bucket) { - // The contexts in the bucket are all different, but the [begin, cursor) range - // is equal for all contexts in the bucket, and the type names are the same if - // |is_broken_down_by_type_name| is set. - DCHECK(!bucket.metrics_by_context.empty()); - - const AllocationContext* context = bucket.metrics_by_context.front().first; - - const StackFrame* backtrace_begin = std::begin(context->backtrace.frames); - const StackFrame* backtrace_end = backtrace_begin + bucket.backtrace_cursor; - DCHECK_LE(bucket.backtrace_cursor, arraysize(context->backtrace.frames)); - - Entry entry; - entry.stack_frame_id = stack_frame_deduplicator_->Insert( - backtrace_begin, backtrace_end); - - // Deduplicate the type name, or use ID -1 if type name is not set. - entry.type_id = bucket.is_broken_down_by_type_name - ? type_name_deduplicator_->Insert(context->type_name) - : -1; - - entry.size = bucket.size; - entry.count = bucket.count; - - auto position_and_inserted = entries_.insert(entry); - return position_and_inserted.second; -} - -void HeapDumpWriter::BreakDown(const Bucket& bucket) { - auto by_backtrace = BreakDownBy(bucket, - BreakDownMode::kByBacktrace, - breakdown_threshold_bytes_); - auto by_type_name = BreakDownBy(bucket, - BreakDownMode::kByTypeName, - breakdown_threshold_bytes_); - - // Insert entries for the buckets. If a bucket was not present before, it has - // not been broken down before, so recursively continue breaking down in that - // case. There might be multiple routes to the same entry (first break down - // by type name, then by backtrace, or first by backtrace and then by type), - // so a set is used to avoid dumping and breaking down entries more than once. - - for (const Bucket& subbucket : by_backtrace) - if (AddEntryForBucket(subbucket)) - BreakDown(subbucket); - - for (const Bucket& subbucket : by_type_name) - if (AddEntryForBucket(subbucket)) - BreakDown(subbucket); -} - -const std::set& HeapDumpWriter::Summarize( - const hash_map& metrics_by_context) { - // Start with one bucket that represents the entire heap. Iterate by - // reference, because the allocation contexts are going to point to allocation - // contexts stored in |metrics_by_context|. - Bucket root_bucket; - for (const auto& context_and_metrics : metrics_by_context) { - DCHECK_GT(context_and_metrics.second.size, 0u); - DCHECK_GT(context_and_metrics.second.count, 0u); - const AllocationContext* context = &context_and_metrics.first; - root_bucket.metrics_by_context.push_back( - std::make_pair(context, context_and_metrics.second)); - root_bucket.size += context_and_metrics.second.size; - root_bucket.count += context_and_metrics.second.count; - } - - AddEntryForBucket(root_bucket); - - // Recursively break down the heap and fill |entries_| with entries to dump. - BreakDown(root_bucket); - - return entries_; -} - -std::unique_ptr Serialize(const std::set& entries) { - std::string buffer; - std::unique_ptr traced_value(new TracedValue); - - traced_value->BeginArray("entries"); - - for (const Entry& entry : entries) { - traced_value->BeginDictionary(); - - // Format size as hexadecimal string into |buffer|. - SStringPrintf(&buffer, "%" PRIx64, static_cast(entry.size)); - traced_value->SetString("size", buffer); - - SStringPrintf(&buffer, "%" PRIx64, static_cast(entry.count)); - traced_value->SetString("count", buffer); - - if (entry.stack_frame_id == -1) { - // An empty backtrace (which will have ID -1) is represented by the empty - // string, because there is no leaf frame to reference in |stackFrames|. - traced_value->SetString("bt", ""); - } else { - // Format index of the leaf frame as a string, because |stackFrames| is a - // dictionary, not an array. - SStringPrintf(&buffer, "%i", entry.stack_frame_id); - traced_value->SetString("bt", buffer); - } - - // Type ID -1 (cumulative size for all types) is represented by the absence - // of the "type" key in the dictionary. - if (entry.type_id != -1) { - // Format the type ID as a string. - SStringPrintf(&buffer, "%i", entry.type_id); - traced_value->SetString("type", buffer); - } - - traced_value->EndDictionary(); - } - - traced_value->EndArray(); // "entries" - return traced_value; -} - -} // namespace internal - -std::unique_ptr ExportHeapDump( - const hash_map& metrics_by_context, - const MemoryDumpSessionState& session_state) { - internal::HeapDumpWriter writer( - session_state.stack_frame_deduplicator(), - session_state.type_name_deduplicator(), - session_state.heap_profiler_breakdown_threshold_bytes()); - return Serialize(writer.Summarize(metrics_by_context)); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/heap_profiler_heap_dump_writer.h b/base/trace_event/heap_profiler_heap_dump_writer.h deleted file mode 100644 index 6e9d29de8782a1258ab70f01b61c106c7eb63661..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_heap_dump_writer.h +++ /dev/null @@ -1,113 +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. - -#ifndef BASE_TRACE_EVENT_HEAP_PROFILER_HEAP_DUMP_WRITER_H_ -#define BASE_TRACE_EVENT_HEAP_PROFILER_HEAP_DUMP_WRITER_H_ - -#include - -#include -#include - -#include "base/base_export.h" -#include "base/containers/hash_tables.h" -#include "base/macros.h" -#include "base/trace_event/heap_profiler_allocation_context.h" - -namespace base { -namespace trace_event { - -class MemoryDumpSessionState; -class StackFrameDeduplicator; -class TracedValue; -class TypeNameDeduplicator; - -// Aggregates |metrics_by_context|, recursively breaks down the heap, and -// returns a traced value with an "entries" array that can be dumped in the -// trace log, following the format described in https://goo.gl/KY7zVE. The -// number of entries is kept reasonable because long tails are not included. -BASE_EXPORT std::unique_ptr ExportHeapDump( - const hash_map& metrics_by_context, - const MemoryDumpSessionState& session_state); - -namespace internal { - -namespace { -struct Bucket; -} - -// An entry in the "entries" array as described in https://goo.gl/KY7zVE. -struct BASE_EXPORT Entry { - size_t size; - size_t count; - - // References a backtrace in the stack frame deduplicator. -1 means empty - // backtrace (the root of the tree). - int stack_frame_id; - - // References a type name in the type name deduplicator. -1 indicates that - // the size is the cumulative size for all types (the root of the tree). - int type_id; -}; - -// Comparison operator to enable putting |Entry| in a |std::set|. -BASE_EXPORT bool operator<(Entry lhs, Entry rhs); - -// Serializes entries to an "entries" array in a traced value. -BASE_EXPORT std::unique_ptr Serialize(const std::set& dump); - -// Helper class to dump a snapshot of an |AllocationRegister| or other heap -// bookkeeping structure into a |TracedValue|. This class is intended to be -// used as a one-shot local instance on the stack. -class BASE_EXPORT HeapDumpWriter { - public: - // The |stack_frame_deduplicator| and |type_name_deduplicator| are not owned. - // The heap dump writer assumes exclusive access to them during the lifetime - // of the dump writer. The heap dumps are broken down for allocations bigger - // than |breakdown_threshold_bytes|. - HeapDumpWriter(StackFrameDeduplicator* stack_frame_deduplicator, - TypeNameDeduplicator* type_name_deduplicator, - uint32_t breakdown_threshold_bytes); - - ~HeapDumpWriter(); - - // Aggregates allocations to compute the total size of the heap, then breaks - // down the heap recursively. This produces the values that should be dumped - // in the "entries" array. The number of entries is kept reasonable because - // long tails are not included. Use |Serialize| to convert to a traced value. - const std::set& Summarize( - const hash_map& metrics_by_context); - - private: - // Inserts an |Entry| for |Bucket| into |entries_|. Returns false if the - // entry was present before, true if it was not. - bool AddEntryForBucket(const Bucket& bucket); - - // Recursively breaks down a bucket into smaller buckets and adds entries for - // the buckets worth dumping to |entries_|. - void BreakDown(const Bucket& bucket); - - // The collection of entries that is filled by |Summarize|. - std::set entries_; - - // Helper for generating the |stackFrames| dictionary. Not owned, must outlive - // this heap dump writer instance. - StackFrameDeduplicator* const stack_frame_deduplicator_; - - // Helper for converting type names to IDs. Not owned, must outlive this heap - // dump writer instance. - TypeNameDeduplicator* const type_name_deduplicator_; - - // Minimum size of an allocation for which an allocation bucket will be - // broken down with children. - uint32_t breakdown_threshold_bytes_; - - DISALLOW_COPY_AND_ASSIGN(HeapDumpWriter); -}; - -} // namespace internal -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_HEAP_PROFILER_HEAP_DUMP_WRITER_H_ diff --git a/base/trace_event/heap_profiler_stack_frame_deduplicator.cc b/base/trace_event/heap_profiler_stack_frame_deduplicator.cc deleted file mode 100644 index fc5da0d1dde9ed15498e1d03574d737c5f004460..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_stack_frame_deduplicator.cc +++ /dev/null @@ -1,131 +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. - -#include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" - -#include -#include - -#include -#include - -#include "base/strings/stringprintf.h" -#include "base/trace_event/memory_usage_estimator.h" -#include "base/trace_event/trace_event_argument.h" -#include "base/trace_event/trace_event_memory_overhead.h" - -namespace base { -namespace trace_event { - -StackFrameDeduplicator::FrameNode::FrameNode(StackFrame frame, - int parent_frame_index) - : frame(frame), parent_frame_index(parent_frame_index) {} -StackFrameDeduplicator::FrameNode::FrameNode(const FrameNode& other) = default; -StackFrameDeduplicator::FrameNode::~FrameNode() {} - -size_t StackFrameDeduplicator::FrameNode::EstimateMemoryUsage() const { - return base::trace_event::EstimateMemoryUsage(children); -} - -StackFrameDeduplicator::StackFrameDeduplicator() {} -StackFrameDeduplicator::~StackFrameDeduplicator() {} - -int StackFrameDeduplicator::Insert(const StackFrame* beginFrame, - const StackFrame* endFrame) { - int frame_index = -1; - std::map* nodes = &roots_; - - // Loop through the frames, early out when a frame is null. - for (const StackFrame* it = beginFrame; it != endFrame; it++) { - StackFrame frame = *it; - - auto node = nodes->find(frame); - if (node == nodes->end()) { - // There is no tree node for this frame yet, create it. The parent node - // is the node associated with the previous frame. - FrameNode frame_node(frame, frame_index); - - // The new frame node will be appended, so its index is the current size - // of the vector. - frame_index = static_cast(frames_.size()); - - // Add the node to the trie so it will be found next time. - nodes->insert(std::make_pair(frame, frame_index)); - - // Append the node after modifying |nodes|, because the |frames_| vector - // might need to resize, and this invalidates the |nodes| pointer. - frames_.push_back(frame_node); - } else { - // A tree node for this frame exists. Look for the next one. - frame_index = node->second; - } - - nodes = &frames_[frame_index].children; - } - - return frame_index; -} - -void StackFrameDeduplicator::AppendAsTraceFormat(std::string* out) const { - out->append("{"); // Begin the |stackFrames| dictionary. - - int i = 0; - auto frame_node = begin(); - auto it_end = end(); - std::string stringify_buffer; - - while (frame_node != it_end) { - // The |stackFrames| format is a dictionary, not an array, so the - // keys are stringified indices. Write the index manually, then use - // |TracedValue| to format the object. This is to avoid building the - // entire dictionary as a |TracedValue| in memory. - SStringPrintf(&stringify_buffer, "\"%d\":", i); - out->append(stringify_buffer); - - std::unique_ptr frame_node_value(new TracedValue); - const StackFrame& frame = frame_node->frame; - switch (frame.type) { - case StackFrame::Type::TRACE_EVENT_NAME: - frame_node_value->SetString( - "name", static_cast(frame.value)); - break; - case StackFrame::Type::THREAD_NAME: - SStringPrintf(&stringify_buffer, - "[Thread: %s]", - static_cast(frame.value)); - frame_node_value->SetString("name", stringify_buffer); - break; - case StackFrame::Type::PROGRAM_COUNTER: - SStringPrintf(&stringify_buffer, - "pc:%" PRIxPTR, - reinterpret_cast(frame.value)); - frame_node_value->SetString("name", stringify_buffer); - break; - } - if (frame_node->parent_frame_index >= 0) { - SStringPrintf(&stringify_buffer, "%d", frame_node->parent_frame_index); - frame_node_value->SetString("parent", stringify_buffer); - } - frame_node_value->AppendAsTraceFormat(out); - - i++; - frame_node++; - - if (frame_node != it_end) - out->append(","); - } - - out->append("}"); // End the |stackFrames| dictionary. -} - -void StackFrameDeduplicator::EstimateTraceMemoryOverhead( - TraceEventMemoryOverhead* overhead) { - size_t memory_usage = - EstimateMemoryUsage(frames_) + EstimateMemoryUsage(roots_); - overhead->Add("StackFrameDeduplicator", - sizeof(StackFrameDeduplicator) + memory_usage); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/heap_profiler_stack_frame_deduplicator.h b/base/trace_event/heap_profiler_stack_frame_deduplicator.h deleted file mode 100644 index 66d430f2ee32572aa0732c4105de410a3ac4db27..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_stack_frame_deduplicator.h +++ /dev/null @@ -1,81 +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. - -#ifndef BASE_TRACE_EVENT_HEAP_PROFILER_STACK_FRAME_DEDUPLICATOR_H_ -#define BASE_TRACE_EVENT_HEAP_PROFILER_STACK_FRAME_DEDUPLICATOR_H_ - -#include -#include -#include - -#include "base/base_export.h" -#include "base/macros.h" -#include "base/trace_event/heap_profiler_allocation_context.h" -#include "base/trace_event/trace_event_impl.h" - -namespace base { -namespace trace_event { - -class TraceEventMemoryOverhead; - -// A data structure that allows grouping a set of backtraces in a space- -// efficient manner by creating a call tree and writing it as a set of (node, -// parent) pairs. The tree nodes reference both parent and children. The parent -// is referenced by index into |frames_|. The children are referenced via a map -// of |StackFrame|s to index into |frames_|. So there is a trie for bottum-up -// lookup of a backtrace for deduplication, and a tree for compact storage in -// the trace log. -class BASE_EXPORT StackFrameDeduplicator : public ConvertableToTraceFormat { - public: - // A node in the call tree. - struct FrameNode { - FrameNode(StackFrame frame, int parent_frame_index); - FrameNode(const FrameNode& other); - ~FrameNode(); - - size_t EstimateMemoryUsage() const; - - StackFrame frame; - - // The index of the parent stack frame in |frames_|, or -1 if there is no - // parent frame (when it is at the bottom of the call stack). - int parent_frame_index; - - // Indices into |frames_| of frames called from the current frame. - std::map children; - }; - - using ConstIterator = std::vector::const_iterator; - - StackFrameDeduplicator(); - ~StackFrameDeduplicator() override; - - // Inserts a backtrace where |beginFrame| is a pointer to the bottom frame - // (e.g. main) and |endFrame| is a pointer past the top frame (most recently - // called function), and returns the index of its leaf node in |frames_|. - // Returns -1 if the backtrace is empty. - int Insert(const StackFrame* beginFrame, const StackFrame* endFrame); - - // Iterators over the frame nodes in the call tree. - ConstIterator begin() const { return frames_.begin(); } - ConstIterator end() const { return frames_.end(); } - - // Writes the |stackFrames| dictionary as defined in https://goo.gl/GerkV8 to - // the trace log. - void AppendAsTraceFormat(std::string* out) const override; - - // Estimates memory overhead including |sizeof(StackFrameDeduplicator)|. - void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead) override; - - private: - std::map roots_; - std::vector frames_; - - DISALLOW_COPY_AND_ASSIGN(StackFrameDeduplicator); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_HEAP_PROFILER_STACK_FRAME_DEDUPLICATOR_H_ diff --git a/base/trace_event/heap_profiler_stack_frame_deduplicator_unittest.cc b/base/trace_event/heap_profiler_stack_frame_deduplicator_unittest.cc deleted file mode 100644 index 2215edebb56fd5920eb7ccade2bb103f3dd9f165..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_stack_frame_deduplicator_unittest.cc +++ /dev/null @@ -1,152 +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. - -#include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" - -#include -#include - -#include "base/macros.h" -#include "base/trace_event/heap_profiler_allocation_context.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace trace_event { - -// Define all strings once, because the deduplicator requires pointer equality, -// and string interning is unreliable. -StackFrame kBrowserMain = StackFrame::FromTraceEventName("BrowserMain"); -StackFrame kRendererMain = StackFrame::FromTraceEventName("RendererMain"); -StackFrame kCreateWidget = StackFrame::FromTraceEventName("CreateWidget"); -StackFrame kInitialize = StackFrame::FromTraceEventName("Initialize"); -StackFrame kMalloc = StackFrame::FromTraceEventName("malloc"); - -TEST(StackFrameDeduplicatorTest, SingleBacktrace) { - StackFrame bt[] = {kBrowserMain, kCreateWidget, kMalloc}; - - // The call tree should look like this (index in brackets). - // - // BrowserMain [0] - // CreateWidget [1] - // malloc [2] - - std::unique_ptr dedup(new StackFrameDeduplicator); - ASSERT_EQ(2, dedup->Insert(std::begin(bt), std::end(bt))); - - auto iter = dedup->begin(); - ASSERT_EQ(kBrowserMain, (iter + 0)->frame); - ASSERT_EQ(-1, (iter + 0)->parent_frame_index); - - ASSERT_EQ(kCreateWidget, (iter + 1)->frame); - ASSERT_EQ(0, (iter + 1)->parent_frame_index); - - ASSERT_EQ(kMalloc, (iter + 2)->frame); - ASSERT_EQ(1, (iter + 2)->parent_frame_index); - - ASSERT_EQ(iter + 3, dedup->end()); -} - -TEST(StackFrameDeduplicatorTest, SingleBacktraceWithNull) { - StackFrame null_frame = StackFrame::FromTraceEventName(nullptr); - StackFrame bt[] = {kBrowserMain, null_frame, kMalloc}; - - // Deduplicator doesn't care about what's inside StackFrames, - // and handles nullptr StackFrame values as any other. - // - // So the call tree should look like this (index in brackets). - // - // BrowserMain [0] - // (null) [1] - // malloc [2] - - std::unique_ptr dedup(new StackFrameDeduplicator); - ASSERT_EQ(2, dedup->Insert(std::begin(bt), std::end(bt))); - - auto iter = dedup->begin(); - ASSERT_EQ(kBrowserMain, (iter + 0)->frame); - ASSERT_EQ(-1, (iter + 0)->parent_frame_index); - - ASSERT_EQ(null_frame, (iter + 1)->frame); - ASSERT_EQ(0, (iter + 1)->parent_frame_index); - - ASSERT_EQ(kMalloc, (iter + 2)->frame); - ASSERT_EQ(1, (iter + 2)->parent_frame_index); - - ASSERT_EQ(iter + 3, dedup->end()); -} - -// Test that there can be different call trees (there can be multiple bottom -// frames). Also verify that frames with the same name but a different caller -// are represented as distinct nodes. -TEST(StackFrameDeduplicatorTest, MultipleRoots) { - StackFrame bt0[] = {kBrowserMain, kCreateWidget}; - StackFrame bt1[] = {kRendererMain, kCreateWidget}; - - // The call tree should look like this (index in brackets). - // - // BrowserMain [0] - // CreateWidget [1] - // RendererMain [2] - // CreateWidget [3] - // - // Note that there will be two instances of CreateWidget, - // with different parents. - - std::unique_ptr dedup(new StackFrameDeduplicator); - ASSERT_EQ(1, dedup->Insert(std::begin(bt0), std::end(bt0))); - ASSERT_EQ(3, dedup->Insert(std::begin(bt1), std::end(bt1))); - - auto iter = dedup->begin(); - ASSERT_EQ(kBrowserMain, (iter + 0)->frame); - ASSERT_EQ(-1, (iter + 0)->parent_frame_index); - - ASSERT_EQ(kCreateWidget, (iter + 1)->frame); - ASSERT_EQ(0, (iter + 1)->parent_frame_index); - - ASSERT_EQ(kRendererMain, (iter + 2)->frame); - ASSERT_EQ(-1, (iter + 2)->parent_frame_index); - - ASSERT_EQ(kCreateWidget, (iter + 3)->frame); - ASSERT_EQ(2, (iter + 3)->parent_frame_index); - - ASSERT_EQ(iter + 4, dedup->end()); -} - -TEST(StackFrameDeduplicatorTest, Deduplication) { - StackFrame bt0[] = {kBrowserMain, kCreateWidget}; - StackFrame bt1[] = {kBrowserMain, kInitialize}; - - // The call tree should look like this (index in brackets). - // - // BrowserMain [0] - // CreateWidget [1] - // Initialize [2] - // - // Note that BrowserMain will be re-used. - - std::unique_ptr dedup(new StackFrameDeduplicator); - ASSERT_EQ(1, dedup->Insert(std::begin(bt0), std::end(bt0))); - ASSERT_EQ(2, dedup->Insert(std::begin(bt1), std::end(bt1))); - - auto iter = dedup->begin(); - ASSERT_EQ(kBrowserMain, (iter + 0)->frame); - ASSERT_EQ(-1, (iter + 0)->parent_frame_index); - - ASSERT_EQ(kCreateWidget, (iter + 1)->frame); - ASSERT_EQ(0, (iter + 1)->parent_frame_index); - - ASSERT_EQ(kInitialize, (iter + 2)->frame); - ASSERT_EQ(0, (iter + 2)->parent_frame_index); - - ASSERT_EQ(iter + 3, dedup->end()); - - // Inserting the same backtrace again should return the index of the existing - // node. - ASSERT_EQ(1, dedup->Insert(std::begin(bt0), std::end(bt0))); - ASSERT_EQ(2, dedup->Insert(std::begin(bt1), std::end(bt1))); - ASSERT_EQ(dedup->begin() + 3, dedup->end()); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/heap_profiler_type_name_deduplicator.cc b/base/trace_event/heap_profiler_type_name_deduplicator.cc deleted file mode 100644 index a6dab51ad2abf24195a76c7f47b4dc0151fc99f7..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_type_name_deduplicator.cc +++ /dev/null @@ -1,115 +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. - -#include "base/trace_event/heap_profiler_type_name_deduplicator.h" - -#include -#include -#include -#include - -#include "base/json/string_escape.h" -#include "base/strings/string_split.h" -#include "base/strings/stringprintf.h" -#include "base/trace_event/memory_usage_estimator.h" -#include "base/trace_event/trace_event.h" -#include "base/trace_event/trace_event_memory_overhead.h" - -namespace base { -namespace trace_event { - -namespace { - -// If |type_name| is file name then extract directory name. Or if |type_name| is -// category name, then disambiguate multple categories and remove -// "disabled-by-default" prefix if present. -StringPiece ExtractCategoryFromTypeName(const char* type_name) { - StringPiece result(type_name); - size_t last_seperator = result.find_last_of("\\/"); - - // If |type_name| was a not a file path, the seperator will not be found, so - // the whole type name is returned. - if (last_seperator == StringPiece::npos) { - // Use the first the category name if it has ",". - size_t first_comma_position = result.find(','); - if (first_comma_position != StringPiece::npos) - result = result.substr(0, first_comma_position); - if (result.starts_with(TRACE_DISABLED_BY_DEFAULT(""))) - result.remove_prefix(sizeof(TRACE_DISABLED_BY_DEFAULT("")) - 1); - return result; - } - - // Remove the file name from the path. - result.remove_suffix(result.length() - last_seperator); - - // Remove the parent directory references. - const char kParentDirectory[] = ".."; - const size_t kParentDirectoryLength = 3; // '../' or '..\'. - while (result.starts_with(kParentDirectory)) { - result.remove_prefix(kParentDirectoryLength); - } - return result; -} - -} // namespace - -TypeNameDeduplicator::TypeNameDeduplicator() { - // A null pointer has type ID 0 ("unknown type"); - type_ids_.insert(std::make_pair(nullptr, 0)); -} - -TypeNameDeduplicator::~TypeNameDeduplicator() {} - -int TypeNameDeduplicator::Insert(const char* type_name) { - auto result = type_ids_.insert(std::make_pair(type_name, 0)); - auto& elem = result.first; - bool did_not_exist_before = result.second; - - if (did_not_exist_before) { - // The type IDs are assigned sequentially and they are zero-based, so - // |size() - 1| is the ID of the new element. - elem->second = static_cast(type_ids_.size() - 1); - } - - return elem->second; -} - -void TypeNameDeduplicator::AppendAsTraceFormat(std::string* out) const { - out->append("{"); // Begin the type names dictionary. - - auto it = type_ids_.begin(); - std::string buffer; - - // Write the first entry manually; the null pointer must not be dereferenced. - // (The first entry is the null pointer because a |std::map| is ordered.) - it++; - out->append("\"0\":\"[unknown]\""); - - for (; it != type_ids_.end(); it++) { - // Type IDs in the trace are strings, write them as stringified keys of - // a dictionary. - SStringPrintf(&buffer, ",\"%d\":", it->second); - - // TODO(ssid): crbug.com/594803 the type name is misused for file name in - // some cases. - StringPiece type_info = ExtractCategoryFromTypeName(it->first); - - // |EscapeJSONString| appends, it does not overwrite |buffer|. - bool put_in_quotes = true; - EscapeJSONString(type_info, put_in_quotes, &buffer); - out->append(buffer); - } - - out->append("}"); // End the type names dictionary. -} - -void TypeNameDeduplicator::EstimateTraceMemoryOverhead( - TraceEventMemoryOverhead* overhead) { - size_t memory_usage = EstimateMemoryUsage(type_ids_); - overhead->Add("TypeNameDeduplicator", - sizeof(TypeNameDeduplicator) + memory_usage); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/heap_profiler_type_name_deduplicator.h b/base/trace_event/heap_profiler_type_name_deduplicator.h deleted file mode 100644 index 2d26c73488ef59b7e7ce8502914a54984d4666f7..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_type_name_deduplicator.h +++ /dev/null @@ -1,45 +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. - -#ifndef BASE_TRACE_EVENT_HEAP_PROFILER_TYPE_NAME_DEDUPLICATOR_H_ -#define BASE_TRACE_EVENT_HEAP_PROFILER_TYPE_NAME_DEDUPLICATOR_H_ - -#include -#include - -#include "base/base_export.h" -#include "base/macros.h" -#include "base/trace_event/trace_event_impl.h" - -namespace base { -namespace trace_event { - -class TraceEventMemoryOverhead; - -// Data structure that assigns a unique numeric ID to |const char*|s. -class BASE_EXPORT TypeNameDeduplicator : public ConvertableToTraceFormat { - public: - TypeNameDeduplicator(); - ~TypeNameDeduplicator() override; - - // Inserts a type name and returns its ID. - int Insert(const char* type_name); - - // Writes the type ID -> type name mapping to the trace log. - void AppendAsTraceFormat(std::string* out) const override; - - // Estimates memory overhead including |sizeof(TypeNameDeduplicator)|. - void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead) override; - - private: - // Map from type name to type ID. - std::map type_ids_; - - DISALLOW_COPY_AND_ASSIGN(TypeNameDeduplicator); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_HEAP_PROFILER_TYPE_NAME_DEDUPLICATOR_H_ diff --git a/base/trace_event/heap_profiler_type_name_deduplicator_unittest.cc b/base/trace_event/heap_profiler_type_name_deduplicator_unittest.cc deleted file mode 100644 index b2e681ab26de025f0060e19ea4a2b16b2469c106..0000000000000000000000000000000000000000 --- a/base/trace_event/heap_profiler_type_name_deduplicator_unittest.cc +++ /dev/null @@ -1,96 +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. - -#include -#include - -#include "base/json/json_reader.h" -#include "base/trace_event/heap_profiler_type_name_deduplicator.h" -#include "base/values.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace trace_event { - -namespace { - -// Define all strings once, because the deduplicator requires pointer equality, -// and string interning is unreliable. -const char kInt[] = "int"; -const char kBool[] = "bool"; -const char kString[] = "string"; -const char kNeedsEscape[] = "\"quotes\""; - -#if defined(OS_POSIX) -const char kTaskFileName[] = "../../base/trace_event/trace_log.cc"; -const char kTaskPath[] = "base/trace_event"; -#else -const char kTaskFileName[] = "..\\..\\base\\memory\\memory_win.cc"; -const char kTaskPath[] = "base\\memory"; -#endif - -std::unique_ptr DumpAndReadBack( - const TypeNameDeduplicator& deduplicator) { - std::string json; - deduplicator.AppendAsTraceFormat(&json); - return JSONReader::Read(json); -} - -// Inserts a single type name into a new TypeNameDeduplicator instance and -// checks if the value gets inserted and the exported value for |type_name| is -// the same as |expected_value|. -void TestInsertTypeAndReadback(const char* type_name, - const char* expected_value) { - std::unique_ptr dedup(new TypeNameDeduplicator); - ASSERT_EQ(1, dedup->Insert(type_name)); - - std::unique_ptr type_names = DumpAndReadBack(*dedup); - ASSERT_NE(nullptr, type_names); - - const DictionaryValue* dictionary; - ASSERT_TRUE(type_names->GetAsDictionary(&dictionary)); - - // When the type name was inserted, it got ID 1. The exported key "1" - // should be equal to |expected_value|. - std::string value; - ASSERT_TRUE(dictionary->GetString("1", &value)); - ASSERT_EQ(expected_value, value); -} - -} // namespace - -TEST(TypeNameDeduplicatorTest, Deduplication) { - // The type IDs should be like this: - // 0: [unknown] - // 1: int - // 2: bool - // 3: string - - std::unique_ptr dedup(new TypeNameDeduplicator); - ASSERT_EQ(1, dedup->Insert(kInt)); - ASSERT_EQ(2, dedup->Insert(kBool)); - ASSERT_EQ(3, dedup->Insert(kString)); - - // Inserting again should return the same IDs. - ASSERT_EQ(2, dedup->Insert(kBool)); - ASSERT_EQ(1, dedup->Insert(kInt)); - ASSERT_EQ(3, dedup->Insert(kString)); - - // A null pointer should yield type ID 0. - ASSERT_EQ(0, dedup->Insert(nullptr)); -} - -TEST(TypeNameDeduplicatorTest, EscapeTypeName) { - // Reading json should not fail, because the type name should have been - // escaped properly and exported value should contain quotes. - TestInsertTypeAndReadback(kNeedsEscape, kNeedsEscape); -} - -TEST(TypeNameDeduplicatorTest, TestExtractFileName) { - // The exported value for passed file name should be the folders in the path. - TestInsertTypeAndReadback(kTaskFileName, kTaskPath); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/malloc_dump_provider.cc b/base/trace_event/malloc_dump_provider.cc deleted file mode 100644 index 7f2706092ee6f7aafc8283ca22e67bad13022c3c..0000000000000000000000000000000000000000 --- a/base/trace_event/malloc_dump_provider.cc +++ /dev/null @@ -1,378 +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. - -#include "base/trace_event/malloc_dump_provider.h" - -#include - -#include "base/allocator/allocator_extension.h" -#include "base/allocator/allocator_shim.h" -#include "base/allocator/features.h" -#include "base/debug/profiler.h" -#include "base/trace_event/heap_profiler_allocation_context.h" -#include "base/trace_event/heap_profiler_allocation_context_tracker.h" -#include "base/trace_event/heap_profiler_allocation_register.h" -#include "base/trace_event/heap_profiler_heap_dump_writer.h" -#include "base/trace_event/process_memory_dump.h" -#include "base/trace_event/trace_event_argument.h" -#include "build/build_config.h" - -#if defined(OS_MACOSX) -#include -#else -#include -#endif -#if defined(OS_WIN) -#include -#endif - -namespace base { -namespace trace_event { - -namespace { -#if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM) - -using allocator::AllocatorDispatch; - -void* HookAlloc(const AllocatorDispatch* self, size_t size, void* context) { - const AllocatorDispatch* const next = self->next; - void* ptr = next->alloc_function(next, size, context); - if (ptr) - MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size); - return ptr; -} - -void* HookZeroInitAlloc(const AllocatorDispatch* self, - size_t n, - size_t size, - void* context) { - const AllocatorDispatch* const next = self->next; - void* ptr = next->alloc_zero_initialized_function(next, n, size, context); - if (ptr) - MallocDumpProvider::GetInstance()->InsertAllocation(ptr, n * size); - return ptr; -} - -void* HookAllocAligned(const AllocatorDispatch* self, - size_t alignment, - size_t size, - void* context) { - const AllocatorDispatch* const next = self->next; - void* ptr = next->alloc_aligned_function(next, alignment, size, context); - if (ptr) - MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size); - return ptr; -} - -void* HookRealloc(const AllocatorDispatch* self, - void* address, - size_t size, - void* context) { - const AllocatorDispatch* const next = self->next; - void* ptr = next->realloc_function(next, address, size, context); - MallocDumpProvider::GetInstance()->RemoveAllocation(address); - if (size > 0) // realloc(size == 0) means free(). - MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size); - return ptr; -} - -void HookFree(const AllocatorDispatch* self, void* address, void* context) { - if (address) - MallocDumpProvider::GetInstance()->RemoveAllocation(address); - const AllocatorDispatch* const next = self->next; - next->free_function(next, address, context); -} - -size_t HookGetSizeEstimate(const AllocatorDispatch* self, - void* address, - void* context) { - const AllocatorDispatch* const next = self->next; - return next->get_size_estimate_function(next, address, context); -} - -unsigned HookBatchMalloc(const AllocatorDispatch* self, - size_t size, - void** results, - unsigned num_requested, - void* context) { - const AllocatorDispatch* const next = self->next; - unsigned count = - next->batch_malloc_function(next, size, results, num_requested, context); - for (unsigned i = 0; i < count; ++i) { - MallocDumpProvider::GetInstance()->InsertAllocation(results[i], size); - } - return count; -} - -void HookBatchFree(const AllocatorDispatch* self, - void** to_be_freed, - unsigned num_to_be_freed, - void* context) { - const AllocatorDispatch* const next = self->next; - for (unsigned i = 0; i < num_to_be_freed; ++i) { - MallocDumpProvider::GetInstance()->RemoveAllocation(to_be_freed[i]); - } - next->batch_free_function(next, to_be_freed, num_to_be_freed, context); -} - -void HookFreeDefiniteSize(const AllocatorDispatch* self, - void* ptr, - size_t size, - void* context) { - if (ptr) - MallocDumpProvider::GetInstance()->RemoveAllocation(ptr); - const AllocatorDispatch* const next = self->next; - next->free_definite_size_function(next, ptr, size, context); -} - -AllocatorDispatch g_allocator_hooks = { - &HookAlloc, /* alloc_function */ - &HookZeroInitAlloc, /* alloc_zero_initialized_function */ - &HookAllocAligned, /* alloc_aligned_function */ - &HookRealloc, /* realloc_function */ - &HookFree, /* free_function */ - &HookGetSizeEstimate, /* get_size_estimate_function */ - &HookBatchMalloc, /* batch_malloc_function */ - &HookBatchFree, /* batch_free_function */ - &HookFreeDefiniteSize, /* free_definite_size_function */ - nullptr, /* next */ -}; -#endif // BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM) - -#if defined(OS_WIN) -// A structure containing some information about a given heap. -struct WinHeapInfo { - size_t committed_size; - size_t uncommitted_size; - size_t allocated_size; - size_t block_count; -}; - -// NOTE: crbug.com/665516 -// Unfortunately, there is no safe way to collect information from secondary -// heaps due to limitations and racy nature of this piece of WinAPI. -void WinHeapMemoryDumpImpl(WinHeapInfo* crt_heap_info) { -#if defined(SYZYASAN) - if (base::debug::IsBinaryInstrumented()) - return; -#endif - - // Iterate through whichever heap our CRT is using. - HANDLE crt_heap = reinterpret_cast(_get_heap_handle()); - ::HeapLock(crt_heap); - PROCESS_HEAP_ENTRY heap_entry; - heap_entry.lpData = nullptr; - // Walk over all the entries in the main heap. - while (::HeapWalk(crt_heap, &heap_entry) != FALSE) { - if ((heap_entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) { - crt_heap_info->allocated_size += heap_entry.cbData; - crt_heap_info->block_count++; - } else if ((heap_entry.wFlags & PROCESS_HEAP_REGION) != 0) { - crt_heap_info->committed_size += heap_entry.Region.dwCommittedSize; - crt_heap_info->uncommitted_size += heap_entry.Region.dwUnCommittedSize; - } - } - CHECK(::HeapUnlock(crt_heap) == TRUE); -} -#endif // defined(OS_WIN) -} // namespace - -// static -const char MallocDumpProvider::kAllocatedObjects[] = "malloc/allocated_objects"; - -// static -MallocDumpProvider* MallocDumpProvider::GetInstance() { - return Singleton>::get(); -} - -MallocDumpProvider::MallocDumpProvider() - : heap_profiler_enabled_(false), tid_dumping_heap_(kInvalidThreadId) {} - -MallocDumpProvider::~MallocDumpProvider() {} - -// Called at trace dump point time. Creates a snapshot the memory counters for -// the current process. -bool MallocDumpProvider::OnMemoryDump(const MemoryDumpArgs& args, - ProcessMemoryDump* pmd) { - size_t total_virtual_size = 0; - size_t resident_size = 0; - size_t allocated_objects_size = 0; - size_t allocated_objects_count = 0; -#if defined(USE_TCMALLOC) - bool res = - allocator::GetNumericProperty("generic.heap_size", &total_virtual_size); - DCHECK(res); - res = allocator::GetNumericProperty("generic.total_physical_bytes", - &resident_size); - DCHECK(res); - res = allocator::GetNumericProperty("generic.current_allocated_bytes", - &allocated_objects_size); - DCHECK(res); -#elif defined(OS_MACOSX) || defined(OS_IOS) - malloc_statistics_t stats = {0}; - malloc_zone_statistics(nullptr, &stats); - total_virtual_size = stats.size_allocated; - allocated_objects_size = stats.size_in_use; - - // Resident size is approximated pretty well by stats.max_size_in_use. - // However, on macOS, freed blocks are both resident and reusable, which is - // semantically equivalent to deallocated. The implementation of libmalloc - // will also only hold a fixed number of freed regions before actually - // starting to deallocate them, so stats.max_size_in_use is also not - // representative of the peak size. As a result, stats.max_size_in_use is - // typically somewhere between actually resident [non-reusable] pages, and - // peak size. This is not very useful, so we just use stats.size_in_use for - // resident_size, even though it's an underestimate and fails to account for - // fragmentation. See - // https://bugs.chromium.org/p/chromium/issues/detail?id=695263#c1. - resident_size = stats.size_in_use; -#elif defined(OS_WIN) - WinHeapInfo main_heap_info = {}; - WinHeapMemoryDumpImpl(&main_heap_info); - total_virtual_size = - main_heap_info.committed_size + main_heap_info.uncommitted_size; - // Resident size is approximated with committed heap size. Note that it is - // possible to do this with better accuracy on windows by intersecting the - // working set with the virtual memory ranges occuipied by the heap. It's not - // clear that this is worth it, as it's fairly expensive to do. - resident_size = main_heap_info.committed_size; - allocated_objects_size = main_heap_info.allocated_size; - allocated_objects_count = main_heap_info.block_count; -#else - struct mallinfo info = mallinfo(); - DCHECK_GE(info.arena + info.hblkhd, info.uordblks); - - // In case of Android's jemalloc |arena| is 0 and the outer pages size is - // reported by |hblkhd|. In case of dlmalloc the total is given by - // |arena| + |hblkhd|. For more details see link: http://goo.gl/fMR8lF. - total_virtual_size = info.arena + info.hblkhd; - resident_size = info.uordblks; - - // Total allocated space is given by |uordblks|. - allocated_objects_size = info.uordblks; -#endif - - MemoryAllocatorDump* outer_dump = pmd->CreateAllocatorDump("malloc"); - outer_dump->AddScalar("virtual_size", MemoryAllocatorDump::kUnitsBytes, - total_virtual_size); - outer_dump->AddScalar(MemoryAllocatorDump::kNameSize, - MemoryAllocatorDump::kUnitsBytes, resident_size); - - MemoryAllocatorDump* inner_dump = pmd->CreateAllocatorDump(kAllocatedObjects); - inner_dump->AddScalar(MemoryAllocatorDump::kNameSize, - MemoryAllocatorDump::kUnitsBytes, - allocated_objects_size); - if (allocated_objects_count != 0) { - inner_dump->AddScalar(MemoryAllocatorDump::kNameObjectCount, - MemoryAllocatorDump::kUnitsObjects, - allocated_objects_count); - } - - if (resident_size > allocated_objects_size) { - // Explicitly specify why is extra memory resident. In tcmalloc it accounts - // for free lists and caches. In mac and ios it accounts for the - // fragmentation and metadata. - MemoryAllocatorDump* other_dump = - pmd->CreateAllocatorDump("malloc/metadata_fragmentation_caches"); - other_dump->AddScalar(MemoryAllocatorDump::kNameSize, - MemoryAllocatorDump::kUnitsBytes, - resident_size - allocated_objects_size); - } - - // Heap profiler dumps. - if (!heap_profiler_enabled_) - return true; - - // The dumps of the heap profiler should be created only when heap profiling - // was enabled (--enable-heap-profiling) AND a DETAILED dump is requested. - // However, when enabled, the overhead of the heap profiler should be always - // reported to avoid oscillations of the malloc total in LIGHT dumps. - - tid_dumping_heap_ = PlatformThread::CurrentId(); - // At this point the Insert/RemoveAllocation hooks will ignore this thread. - // Enclosing all the temporariy data structures in a scope, so that the heap - // profiler does not see unabalanced malloc/free calls from these containers. - { - TraceEventMemoryOverhead overhead; - hash_map metrics_by_context; - { - AutoLock lock(allocation_register_lock_); - if (allocation_register_) { - if (args.level_of_detail == MemoryDumpLevelOfDetail::DETAILED) { - for (const auto& alloc_size : *allocation_register_) { - AllocationMetrics& metrics = metrics_by_context[alloc_size.context]; - metrics.size += alloc_size.size; - metrics.count++; - } - } - allocation_register_->EstimateTraceMemoryOverhead(&overhead); - } - } // lock(allocation_register_lock_) - pmd->DumpHeapUsage(metrics_by_context, overhead, "malloc"); - } - tid_dumping_heap_ = kInvalidThreadId; - - return true; -} - -void MallocDumpProvider::OnHeapProfilingEnabled(bool enabled) { -#if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM) - if (enabled) { - { - AutoLock lock(allocation_register_lock_); - allocation_register_.reset(new AllocationRegister()); - } - allocator::InsertAllocatorDispatch(&g_allocator_hooks); - } else { - AutoLock lock(allocation_register_lock_); - allocation_register_.reset(); - // Insert/RemoveAllocation below will no-op if the register is torn down. - // Once disabled, heap profiling will not re-enabled anymore for the - // lifetime of the process. - } -#endif - heap_profiler_enabled_ = enabled; -} - -void MallocDumpProvider::InsertAllocation(void* address, size_t size) { - // CurrentId() can be a slow operation (crbug.com/497226). This apparently - // redundant condition short circuits the CurrentID() calls when unnecessary. - if (tid_dumping_heap_ != kInvalidThreadId && - tid_dumping_heap_ == PlatformThread::CurrentId()) - return; - - // AllocationContextTracker will return nullptr when called re-reentrantly. - // This is the case of GetInstanceForCurrentThread() being called for the - // first time, which causes a new() inside the tracker which re-enters the - // heap profiler, in which case we just want to early out. - auto* tracker = AllocationContextTracker::GetInstanceForCurrentThread(); - if (!tracker) - return; - - AllocationContext context; - if (!tracker->GetContextSnapshot(&context)) - return; - - AutoLock lock(allocation_register_lock_); - if (!allocation_register_) - return; - - allocation_register_->Insert(address, size, context); -} - -void MallocDumpProvider::RemoveAllocation(void* address) { - // No re-entrancy is expected here as none of the calls below should - // cause a free()-s (|allocation_register_| does its own heap management). - if (tid_dumping_heap_ != kInvalidThreadId && - tid_dumping_heap_ == PlatformThread::CurrentId()) - return; - AutoLock lock(allocation_register_lock_); - if (!allocation_register_) - return; - allocation_register_->Remove(address); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/malloc_dump_provider.h b/base/trace_event/malloc_dump_provider.h deleted file mode 100644 index 384033c9b82abc55125405e9a29ede7aa9597c08..0000000000000000000000000000000000000000 --- a/base/trace_event/malloc_dump_provider.h +++ /dev/null @@ -1,69 +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. - -#ifndef BASE_TRACE_EVENT_MALLOC_DUMP_PROVIDER_H_ -#define BASE_TRACE_EVENT_MALLOC_DUMP_PROVIDER_H_ - -#include -#include - -#include "base/macros.h" -#include "base/memory/singleton.h" -#include "base/synchronization/lock.h" -#include "base/threading/platform_thread.h" -#include "base/trace_event/memory_dump_provider.h" -#include "build/build_config.h" - -#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_WIN) || \ - (defined(OS_MACOSX) && !defined(OS_IOS)) -#define MALLOC_MEMORY_TRACING_SUPPORTED -#endif - -namespace base { -namespace trace_event { - -class AllocationRegister; - -// Dump provider which collects process-wide memory stats. -class BASE_EXPORT MallocDumpProvider : public MemoryDumpProvider { - public: - // Name of the allocated_objects dump. Use this to declare suballocator dumps - // from other dump providers. - static const char kAllocatedObjects[]; - - static MallocDumpProvider* GetInstance(); - - // MemoryDumpProvider implementation. - bool OnMemoryDump(const MemoryDumpArgs& args, - ProcessMemoryDump* pmd) override; - - void OnHeapProfilingEnabled(bool enabled) override; - - // For heap profiling. - void InsertAllocation(void* address, size_t size); - void RemoveAllocation(void* address); - - private: - friend struct DefaultSingletonTraits; - - MallocDumpProvider(); - ~MallocDumpProvider() override; - - // For heap profiling. - bool heap_profiler_enabled_; - std::unique_ptr allocation_register_; - Lock allocation_register_lock_; - - // When in OnMemoryDump(), this contains the current thread ID. - // This is to prevent re-entrancy in the heap profiler when the heap dump - // generation is malloc/new-ing for its own bookeeping data structures. - PlatformThreadId tid_dumping_heap_; - - DISALLOW_COPY_AND_ASSIGN(MallocDumpProvider); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_MALLOC_DUMP_PROVIDER_H_ diff --git a/base/trace_event/memory_allocator_dump.cc b/base/trace_event/memory_allocator_dump.cc deleted file mode 100644 index 2692521c096ab6213cfe1dd20d2a98e8a91ff6ce..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_allocator_dump.cc +++ /dev/null @@ -1,110 +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. - -#include "base/trace_event/memory_allocator_dump.h" - -#include "base/format_macros.h" -#include "base/strings/stringprintf.h" -#include "base/trace_event/memory_dump_manager.h" -#include "base/trace_event/memory_dump_provider.h" -#include "base/trace_event/process_memory_dump.h" -#include "base/trace_event/trace_event_argument.h" -#include "base/values.h" - -namespace base { -namespace trace_event { - -const char MemoryAllocatorDump::kNameSize[] = "size"; -const char MemoryAllocatorDump::kNameObjectCount[] = "object_count"; -const char MemoryAllocatorDump::kTypeScalar[] = "scalar"; -const char MemoryAllocatorDump::kTypeString[] = "string"; -const char MemoryAllocatorDump::kUnitsBytes[] = "bytes"; -const char MemoryAllocatorDump::kUnitsObjects[] = "objects"; - -MemoryAllocatorDump::MemoryAllocatorDump(const std::string& absolute_name, - ProcessMemoryDump* process_memory_dump, - const MemoryAllocatorDumpGuid& guid) - : absolute_name_(absolute_name), - process_memory_dump_(process_memory_dump), - attributes_(new TracedValue), - guid_(guid), - flags_(Flags::DEFAULT), - size_(0) { - // The |absolute_name| cannot be empty. - DCHECK(!absolute_name.empty()); - - // The |absolute_name| can contain slash separator, but not leading or - // trailing ones. - DCHECK(absolute_name[0] != '/' && *absolute_name.rbegin() != '/'); -} - -// If the caller didn't provide a guid, make one up by hashing the -// absolute_name with the current PID. -// Rationale: |absolute_name| is already supposed to be unique within a -// process, the pid will make it unique among all processes. -MemoryAllocatorDump::MemoryAllocatorDump(const std::string& absolute_name, - ProcessMemoryDump* process_memory_dump) - : MemoryAllocatorDump(absolute_name, - process_memory_dump, - MemoryAllocatorDumpGuid(StringPrintf( - "%d:%s", - TraceLog::GetInstance()->process_id(), - absolute_name.c_str()))) { - string_conversion_buffer_.reserve(16); -} - -MemoryAllocatorDump::~MemoryAllocatorDump() { -} - -void MemoryAllocatorDump::AddScalar(const char* name, - const char* units, - uint64_t value) { - if (strcmp(kNameSize, name) == 0) - size_ = value; - SStringPrintf(&string_conversion_buffer_, "%" PRIx64, value); - attributes_->BeginDictionary(name); - attributes_->SetString("type", kTypeScalar); - attributes_->SetString("units", units); - attributes_->SetString("value", string_conversion_buffer_); - attributes_->EndDictionary(); -} - -void MemoryAllocatorDump::AddScalarF(const char* name, - const char* units, - double value) { - attributes_->BeginDictionary(name); - attributes_->SetString("type", kTypeScalar); - attributes_->SetString("units", units); - attributes_->SetDouble("value", value); - attributes_->EndDictionary(); -} - -void MemoryAllocatorDump::AddString(const char* name, - const char* units, - const std::string& value) { - // String attributes are disabled in background mode. - if (process_memory_dump_->dump_args().level_of_detail == - MemoryDumpLevelOfDetail::BACKGROUND) { - NOTREACHED(); - return; - } - - attributes_->BeginDictionary(name); - attributes_->SetString("type", kTypeString); - attributes_->SetString("units", units); - attributes_->SetString("value", value); - attributes_->EndDictionary(); -} - -void MemoryAllocatorDump::AsValueInto(TracedValue* value) const { - value->BeginDictionaryWithCopiedName(absolute_name_); - value->SetString("guid", guid_.ToString()); - value->SetValue("attrs", *attributes_); - if (flags_) - value->SetInteger("flags", flags_); - value->EndDictionary(); // "allocator_name/heap_subheap": { ... } -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_allocator_dump.h b/base/trace_event/memory_allocator_dump.h deleted file mode 100644 index 99ff114e5c78a28dedb2667e7c3a1b95a1726f82..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_allocator_dump.h +++ /dev/null @@ -1,115 +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. - -#ifndef BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_H_ -#define BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_H_ - -#include - -#include -#include - -#include "base/base_export.h" -#include "base/gtest_prod_util.h" -#include "base/logging.h" -#include "base/macros.h" -#include "base/trace_event/memory_allocator_dump_guid.h" -#include "base/values.h" - -namespace base { -namespace trace_event { - -class ProcessMemoryDump; -class TracedValue; - -// Data model for user-land memory allocator dumps. -class BASE_EXPORT MemoryAllocatorDump { - public: - enum Flags { - DEFAULT = 0, - - // A dump marked weak will be discarded by TraceViewer. - WEAK = 1 << 0, - }; - - // MemoryAllocatorDump is owned by ProcessMemoryDump. - MemoryAllocatorDump(const std::string& absolute_name, - ProcessMemoryDump* process_memory_dump, - const MemoryAllocatorDumpGuid& guid); - MemoryAllocatorDump(const std::string& absolute_name, - ProcessMemoryDump* process_memory_dump); - ~MemoryAllocatorDump(); - - // Standard attribute |name|s for the AddScalar and AddString() methods. - static const char kNameSize[]; // To represent allocated space. - static const char kNameObjectCount[]; // To represent number of objects. - - // Standard attribute |unit|s for the AddScalar and AddString() methods. - static const char kUnitsBytes[]; // Unit name to represent bytes. - static const char kUnitsObjects[]; // Unit name to represent #objects. - - // Constants used only internally and by tests. - static const char kTypeScalar[]; // Type name for scalar attributes. - static const char kTypeString[]; // Type name for string attributes. - - // Setters for scalar attributes. Some examples: - // - "size" column (all dumps are expected to have at least this one): - // AddScalar(kNameSize, kUnitsBytes, 1234); - // - Some extra-column reporting internal details of the subsystem: - // AddScalar("number_of_freelist_entires", kUnitsObjects, 42) - // - Other informational column (will not be auto-added in the UI) - // AddScalarF("kittens_ratio", "ratio", 42.0f) - void AddScalar(const char* name, const char* units, uint64_t value); - void AddScalarF(const char* name, const char* units, double value); - void AddString(const char* name, const char* units, const std::string& value); - - // Absolute name, unique within the scope of an entire ProcessMemoryDump. - const std::string& absolute_name() const { return absolute_name_; } - - // Called at trace generation time to populate the TracedValue. - void AsValueInto(TracedValue* value) const; - - // Use enum Flags to set values. - void set_flags(int flags) { flags_ |= flags; } - void clear_flags(int flags) { flags_ &= ~flags; } - int flags() { return flags_; } - - // |guid| is an optional global dump identifier, unique across all processes - // within the scope of a global dump. It is only required when using the - // graph APIs (see TODO_method_name) to express retention / suballocation or - // cross process sharing. See crbug.com/492102 for design docs. - // Subsequent MemoryAllocatorDump(s) with the same |absolute_name| are - // expected to have the same guid. - const MemoryAllocatorDumpGuid& guid() const { return guid_; } - - TracedValue* attributes_for_testing() const { return attributes_.get(); } - - private: - // TODO(hjd): Transitional until we send the full PMD. See crbug.com/704203 - friend class MemoryDumpManager; - FRIEND_TEST_ALL_PREFIXES(MemoryAllocatorDumpTest, GetSize); - - // Get the size for this dump. - // The size is the value set with AddScalar(kNameSize, kUnitsBytes, size); - // TODO(hjd): Transitional until we send the full PMD. See crbug.com/704203 - uint64_t GetSize() const { return size_; }; - - const std::string absolute_name_; - ProcessMemoryDump* const process_memory_dump_; // Not owned (PMD owns this). - std::unique_ptr attributes_; - MemoryAllocatorDumpGuid guid_; - int flags_; // See enum Flags. - uint64_t size_; - - // A local buffer for Sprintf conversion on fastpath. Avoids allocating - // temporary strings on each AddScalar() call. - std::string string_conversion_buffer_; - - DISALLOW_COPY_AND_ASSIGN(MemoryAllocatorDump); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_H_ diff --git a/base/trace_event/memory_allocator_dump_guid.cc b/base/trace_event/memory_allocator_dump_guid.cc deleted file mode 100644 index bf4389a4c759f5ae29bc6fda2cb8c4b4cc536871..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_allocator_dump_guid.cc +++ /dev/null @@ -1,38 +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. - -#include "base/trace_event/memory_allocator_dump_guid.h" - -#include "base/format_macros.h" -#include "base/sha1.h" -#include "base/strings/stringprintf.h" - -namespace base { -namespace trace_event { - -namespace { -uint64_t HashString(const std::string& str) { - uint64_t hash[(kSHA1Length + sizeof(uint64_t) - 1) / sizeof(uint64_t)] = {0}; - SHA1HashBytes(reinterpret_cast(str.data()), str.size(), - reinterpret_cast(hash)); - return hash[0]; -} -} // namespace - -MemoryAllocatorDumpGuid::MemoryAllocatorDumpGuid(uint64_t guid) : guid_(guid) {} - -MemoryAllocatorDumpGuid::MemoryAllocatorDumpGuid() - : MemoryAllocatorDumpGuid(0u) { -} - -MemoryAllocatorDumpGuid::MemoryAllocatorDumpGuid(const std::string& guid_str) - : MemoryAllocatorDumpGuid(HashString(guid_str)) { -} - -std::string MemoryAllocatorDumpGuid::ToString() const { - return StringPrintf("%" PRIx64, guid_); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_allocator_dump_guid.h b/base/trace_event/memory_allocator_dump_guid.h deleted file mode 100644 index b6472c66129bda37c95817984c796e6a6be1dfff..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_allocator_dump_guid.h +++ /dev/null @@ -1,51 +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. - -#ifndef BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_GUID_H_ -#define BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_GUID_H_ - -#include - -#include - -#include "base/base_export.h" - -namespace base { -namespace trace_event { - -class BASE_EXPORT MemoryAllocatorDumpGuid { - public: - MemoryAllocatorDumpGuid(); - explicit MemoryAllocatorDumpGuid(uint64_t guid); - - // Utility ctor to hash a GUID if the caller prefers a string. The caller - // still has to ensure that |guid_str| is unique, per snapshot, within the - // global scope of all the traced processes. - explicit MemoryAllocatorDumpGuid(const std::string& guid_str); - - uint64_t ToUint64() const { return guid_; } - - // Returns a (hex-encoded) string representation of the guid. - std::string ToString() const; - - bool empty() const { return guid_ == 0u; } - - bool operator==(const MemoryAllocatorDumpGuid& other) const { - return guid_ == other.guid_; - } - - bool operator!=(const MemoryAllocatorDumpGuid& other) const { - return !(*this == other); - } - - private: - uint64_t guid_; - - // Deliberately copy-able. -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_GUID_H_ diff --git a/base/trace_event/memory_allocator_dump_unittest.cc b/base/trace_event/memory_allocator_dump_unittest.cc deleted file mode 100644 index e1818f6eeccdad51e1aa961475a8dcfd42ddd7f0..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_allocator_dump_unittest.cc +++ /dev/null @@ -1,200 +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. - -#include "base/trace_event/memory_allocator_dump.h" - -#include - -#include "base/format_macros.h" -#include "base/strings/stringprintf.h" -#include "base/trace_event/memory_allocator_dump_guid.h" -#include "base/trace_event/memory_dump_provider.h" -#include "base/trace_event/memory_dump_session_state.h" -#include "base/trace_event/process_memory_dump.h" -#include "base/trace_event/trace_event_argument.h" -#include "base/values.h" -#include "build/build_config.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace trace_event { - -namespace { - -class FakeMemoryAllocatorDumpProvider : public MemoryDumpProvider { - public: - bool OnMemoryDump(const MemoryDumpArgs& args, - ProcessMemoryDump* pmd) override { - MemoryAllocatorDump* root_heap = - pmd->CreateAllocatorDump("foobar_allocator"); - - root_heap->AddScalar(MemoryAllocatorDump::kNameSize, - MemoryAllocatorDump::kUnitsBytes, 4096); - root_heap->AddScalar(MemoryAllocatorDump::kNameObjectCount, - MemoryAllocatorDump::kUnitsObjects, 42); - root_heap->AddScalar("attr1", "units1", 1234); - root_heap->AddString("attr2", "units2", "string_value"); - root_heap->AddScalarF("attr3", "units3", 42.5f); - - MemoryAllocatorDump* sub_heap = - pmd->CreateAllocatorDump("foobar_allocator/sub_heap"); - sub_heap->AddScalar(MemoryAllocatorDump::kNameSize, - MemoryAllocatorDump::kUnitsBytes, 1); - sub_heap->AddScalar(MemoryAllocatorDump::kNameObjectCount, - MemoryAllocatorDump::kUnitsObjects, 3); - - pmd->CreateAllocatorDump("foobar_allocator/sub_heap/empty"); - // Leave the rest of sub heap deliberately uninitialized, to check that - // CreateAllocatorDump returns a properly zero-initialized object. - - return true; - } -}; - -std::unique_ptr CheckAttribute(const MemoryAllocatorDump* dump, - const std::string& name, - const char* expected_type, - const char* expected_units) { - std::unique_ptr raw_attrs = - dump->attributes_for_testing()->ToBaseValue(); - DictionaryValue* args = nullptr; - DictionaryValue* arg = nullptr; - std::string arg_value; - const Value* out_value = nullptr; - EXPECT_TRUE(raw_attrs->GetAsDictionary(&args)); - EXPECT_TRUE(args->GetDictionary(name, &arg)); - EXPECT_TRUE(arg->GetString("type", &arg_value)); - EXPECT_EQ(expected_type, arg_value); - EXPECT_TRUE(arg->GetString("units", &arg_value)); - EXPECT_EQ(expected_units, arg_value); - EXPECT_TRUE(arg->Get("value", &out_value)); - return out_value ? out_value->CreateDeepCopy() : std::unique_ptr(); -} - -void CheckString(const MemoryAllocatorDump* dump, - const std::string& name, - const char* expected_type, - const char* expected_units, - const std::string& expected_value) { - std::string attr_str_value; - auto attr_value = CheckAttribute(dump, name, expected_type, expected_units); - EXPECT_TRUE(attr_value->GetAsString(&attr_str_value)); - EXPECT_EQ(expected_value, attr_str_value); -} - -void CheckScalar(const MemoryAllocatorDump* dump, - const std::string& name, - const char* expected_units, - uint64_t expected_value) { - CheckString(dump, name, MemoryAllocatorDump::kTypeScalar, expected_units, - StringPrintf("%" PRIx64, expected_value)); -} - -void CheckScalarF(const MemoryAllocatorDump* dump, - const std::string& name, - const char* expected_units, - double expected_value) { - auto attr_value = CheckAttribute(dump, name, MemoryAllocatorDump::kTypeScalar, - expected_units); - double attr_double_value; - EXPECT_TRUE(attr_value->GetAsDouble(&attr_double_value)); - EXPECT_EQ(expected_value, attr_double_value); -} - -} // namespace - -TEST(MemoryAllocatorDumpTest, GuidGeneration) { - std::unique_ptr mad( - new MemoryAllocatorDump("foo", nullptr, MemoryAllocatorDumpGuid(0x42u))); - ASSERT_EQ("42", mad->guid().ToString()); - - // If the dumper does not provide a Guid, the MAD will make one up on the - // flight. Furthermore that Guid will stay stable across across multiple - // snapshots if the |absolute_name| of the dump doesn't change - mad.reset(new MemoryAllocatorDump("bar", nullptr)); - const MemoryAllocatorDumpGuid guid_bar = mad->guid(); - ASSERT_FALSE(guid_bar.empty()); - ASSERT_FALSE(guid_bar.ToString().empty()); - ASSERT_EQ(guid_bar, mad->guid()); - - mad.reset(new MemoryAllocatorDump("bar", nullptr)); - const MemoryAllocatorDumpGuid guid_bar_2 = mad->guid(); - ASSERT_EQ(guid_bar, guid_bar_2); - - mad.reset(new MemoryAllocatorDump("baz", nullptr)); - const MemoryAllocatorDumpGuid guid_baz = mad->guid(); - ASSERT_NE(guid_bar, guid_baz); -} - -TEST(MemoryAllocatorDumpTest, DumpIntoProcessMemoryDump) { - FakeMemoryAllocatorDumpProvider fmadp; - MemoryDumpArgs dump_args = {MemoryDumpLevelOfDetail::DETAILED}; - ProcessMemoryDump pmd(new MemoryDumpSessionState, dump_args); - - fmadp.OnMemoryDump(dump_args, &pmd); - - ASSERT_EQ(3u, pmd.allocator_dumps().size()); - - const MemoryAllocatorDump* root_heap = - pmd.GetAllocatorDump("foobar_allocator"); - ASSERT_NE(nullptr, root_heap); - EXPECT_EQ("foobar_allocator", root_heap->absolute_name()); - CheckScalar(root_heap, MemoryAllocatorDump::kNameSize, - MemoryAllocatorDump::kUnitsBytes, 4096); - CheckScalar(root_heap, MemoryAllocatorDump::kNameObjectCount, - MemoryAllocatorDump::kUnitsObjects, 42); - CheckScalar(root_heap, "attr1", "units1", 1234); - CheckString(root_heap, "attr2", MemoryAllocatorDump::kTypeString, "units2", - "string_value"); - CheckScalarF(root_heap, "attr3", "units3", 42.5f); - - const MemoryAllocatorDump* sub_heap = - pmd.GetAllocatorDump("foobar_allocator/sub_heap"); - ASSERT_NE(nullptr, sub_heap); - EXPECT_EQ("foobar_allocator/sub_heap", sub_heap->absolute_name()); - CheckScalar(sub_heap, MemoryAllocatorDump::kNameSize, - MemoryAllocatorDump::kUnitsBytes, 1); - CheckScalar(sub_heap, MemoryAllocatorDump::kNameObjectCount, - MemoryAllocatorDump::kUnitsObjects, 3); - const MemoryAllocatorDump* empty_sub_heap = - pmd.GetAllocatorDump("foobar_allocator/sub_heap/empty"); - ASSERT_NE(nullptr, empty_sub_heap); - EXPECT_EQ("foobar_allocator/sub_heap/empty", empty_sub_heap->absolute_name()); - auto raw_attrs = empty_sub_heap->attributes_for_testing()->ToBaseValue(); - DictionaryValue* attrs = nullptr; - ASSERT_TRUE(raw_attrs->GetAsDictionary(&attrs)); - ASSERT_FALSE(attrs->HasKey(MemoryAllocatorDump::kNameSize)); - ASSERT_FALSE(attrs->HasKey(MemoryAllocatorDump::kNameObjectCount)); - - // Check that the AsValueInfo doesn't hit any DCHECK. - std::unique_ptr traced_value(new TracedValue); - pmd.AsValueInto(traced_value.get()); -} - -TEST(MemoryAllocatorDumpTest, GetSize) { - MemoryDumpArgs dump_args = {MemoryDumpLevelOfDetail::DETAILED}; - ProcessMemoryDump pmd(new MemoryDumpSessionState, dump_args); - MemoryAllocatorDump* dump = pmd.CreateAllocatorDump("allocator_for_size"); - dump->AddScalar(MemoryAllocatorDump::kNameSize, - MemoryAllocatorDump::kUnitsBytes, 1); - dump->AddScalar("foo", MemoryAllocatorDump::kUnitsBytes, 2); - EXPECT_EQ(1u, dump->GetSize()); -} - -// DEATH tests are not supported in Android / iOS. -#if !defined(NDEBUG) && !defined(OS_ANDROID) && !defined(OS_IOS) -TEST(MemoryAllocatorDumpTest, ForbidDuplicatesDeathTest) { - FakeMemoryAllocatorDumpProvider fmadp; - MemoryDumpArgs dump_args = {MemoryDumpLevelOfDetail::DETAILED}; - ProcessMemoryDump pmd(new MemoryDumpSessionState, dump_args); - pmd.CreateAllocatorDump("foo_allocator"); - pmd.CreateAllocatorDump("bar_allocator/heap"); - ASSERT_DEATH(pmd.CreateAllocatorDump("foo_allocator"), ""); - ASSERT_DEATH(pmd.CreateAllocatorDump("bar_allocator/heap"), ""); - ASSERT_DEATH(pmd.CreateAllocatorDump(""), ""); -} -#endif - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_dump_manager.cc b/base/trace_event/memory_dump_manager.cc deleted file mode 100644 index 6ed1ca8fff3aa55d94af524a1a0e0057183b02ac..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_manager.cc +++ /dev/null @@ -1,991 +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. - -#include "base/trace_event/memory_dump_manager.h" - -#include -#include - -#include -#include - -#include "base/allocator/features.h" -#include "base/atomic_sequence_num.h" -#include "base/base_switches.h" -#include "base/command_line.h" -#include "base/compiler_specific.h" -#include "base/debug/alias.h" -#include "base/debug/debugging_flags.h" -#include "base/debug/stack_trace.h" -#include "base/debug/thread_heap_usage_tracker.h" -#include "base/memory/ptr_util.h" -#include "base/strings/pattern.h" -#include "base/strings/string_piece.h" -#include "base/threading/thread.h" -#include "base/threading/thread_task_runner_handle.h" -#include "base/trace_event/heap_profiler.h" -#include "base/trace_event/heap_profiler_allocation_context_tracker.h" -#include "base/trace_event/heap_profiler_event_filter.h" -#include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" -#include "base/trace_event/heap_profiler_type_name_deduplicator.h" -#include "base/trace_event/malloc_dump_provider.h" -#include "base/trace_event/memory_dump_provider.h" -#include "base/trace_event/memory_dump_scheduler.h" -#include "base/trace_event/memory_dump_session_state.h" -#include "base/trace_event/memory_infra_background_whitelist.h" -#include "base/trace_event/process_memory_dump.h" -#include "base/trace_event/trace_event.h" -#include "base/trace_event/trace_event_argument.h" -#include "build/build_config.h" - -#if defined(OS_ANDROID) -#include "base/trace_event/java_heap_dump_provider_android.h" -#endif - -namespace base { -namespace trace_event { - -namespace { - -const int kTraceEventNumArgs = 1; -const char* kTraceEventArgNames[] = {"dumps"}; -const unsigned char kTraceEventArgTypes[] = {TRACE_VALUE_TYPE_CONVERTABLE}; - -StaticAtomicSequenceNumber g_next_guid; -MemoryDumpManager* g_instance_for_testing = nullptr; - -// The list of names of dump providers that are blacklisted from strict thread -// affinity check on unregistration. These providers could potentially cause -// crashes on build bots if they do not unregister on right thread. -// TODO(ssid): Fix all the dump providers to unregister if needed and clear the -// blacklist, crbug.com/643438. -const char* const kStrictThreadCheckBlacklist[] = { - "ClientDiscardableSharedMemoryManager", - "ContextProviderCommandBuffer", - "DiscardableSharedMemoryManager", - "FontCaches", - "GpuMemoryBufferVideoFramePool", - "IndexedDBBackingStore", - "Sql", - "ThreadLocalEventBuffer", - "TraceLog", - "URLRequestContext", - "VpxVideoDecoder", - "cc::SoftwareImageDecodeCache", - "cc::StagingBufferPool", - "gpu::BufferManager", - "gpu::MappedMemoryManager", - "gpu::RenderbufferManager", - "BlacklistTestDumpProvider" // for testing -}; - -// Callback wrapper to hook upon the completion of RequestGlobalDump() and -// inject trace markers. -void OnGlobalDumpDone(MemoryDumpCallback wrapped_callback, - uint64_t dump_guid, - bool success) { - char guid_str[20]; - sprintf(guid_str, "0x%" PRIx64, dump_guid); - TRACE_EVENT_NESTABLE_ASYNC_END2(MemoryDumpManager::kTraceCategory, - "GlobalMemoryDump", TRACE_ID_LOCAL(dump_guid), - "dump_guid", TRACE_STR_COPY(guid_str), - "success", success); - - if (!wrapped_callback.is_null()) { - wrapped_callback.Run(dump_guid, success); - wrapped_callback.Reset(); - } -} - -// Proxy class which wraps a ConvertableToTraceFormat owned by the -// |session_state| into a proxy object that can be added to the trace event log. -// This is to solve the problem that the MemoryDumpSessionState is refcounted -// but the tracing subsystem wants a std::unique_ptr. -template -struct SessionStateConvertableProxy : public ConvertableToTraceFormat { - using GetterFunctPtr = T* (MemoryDumpSessionState::*)() const; - - SessionStateConvertableProxy( - scoped_refptr session_state, - GetterFunctPtr getter_function) - : session_state(session_state), getter_function(getter_function) {} - - void AppendAsTraceFormat(std::string* out) const override { - return (session_state.get()->*getter_function)()->AppendAsTraceFormat(out); - } - - void EstimateTraceMemoryOverhead( - TraceEventMemoryOverhead* overhead) override { - return (session_state.get()->*getter_function)() - ->EstimateTraceMemoryOverhead(overhead); - } - - scoped_refptr session_state; - GetterFunctPtr const getter_function; -}; - -} // namespace - -// static -const char* const MemoryDumpManager::kTraceCategory = - TRACE_DISABLED_BY_DEFAULT("memory-infra"); - -// static -const char* const MemoryDumpManager::kLogPrefix = "Memory-infra dump"; - -// static -const int MemoryDumpManager::kMaxConsecutiveFailuresCount = 3; - -// static -const uint64_t MemoryDumpManager::kInvalidTracingProcessId = 0; - -// static -const char* const MemoryDumpManager::kSystemAllocatorPoolName = -#if defined(MALLOC_MEMORY_TRACING_SUPPORTED) - MallocDumpProvider::kAllocatedObjects; -#else - nullptr; -#endif - -// static -MemoryDumpManager* MemoryDumpManager::GetInstance() { - if (g_instance_for_testing) - return g_instance_for_testing; - - return Singleton>::get(); -} - -// static -void MemoryDumpManager::SetInstanceForTesting(MemoryDumpManager* instance) { - g_instance_for_testing = instance; -} - -MemoryDumpManager::MemoryDumpManager() - : memory_tracing_enabled_(0), - tracing_process_id_(kInvalidTracingProcessId), - dumper_registrations_ignored_for_testing_(false), - heap_profiling_enabled_(false) { - g_next_guid.GetNext(); // Make sure that first guid is not zero. - - // At this point the command line may not be initialized but we try to - // enable the heap profiler to capture allocations as soon as possible. - EnableHeapProfilingIfNeeded(); - - strict_thread_check_blacklist_.insert(std::begin(kStrictThreadCheckBlacklist), - std::end(kStrictThreadCheckBlacklist)); -} - -MemoryDumpManager::~MemoryDumpManager() { - TraceLog::GetInstance()->RemoveEnabledStateObserver(this); -} - -void MemoryDumpManager::EnableHeapProfilingIfNeeded() { - if (heap_profiling_enabled_) - return; - - if (!CommandLine::InitializedForCurrentProcess() || - !CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnableHeapProfiling)) - return; - - std::string profiling_mode = CommandLine::ForCurrentProcess() - ->GetSwitchValueASCII(switches::kEnableHeapProfiling); - if (profiling_mode == "") { - AllocationContextTracker::SetCaptureMode( - AllocationContextTracker::CaptureMode::PSEUDO_STACK); -#if HAVE_TRACE_STACK_FRAME_POINTERS && \ - (BUILDFLAG(ENABLE_PROFILING) || !defined(NDEBUG)) - } else if (profiling_mode == switches::kEnableHeapProfilingModeNative) { - // We need frame pointers for native tracing to work, and they are - // enabled in profiling and debug builds. - AllocationContextTracker::SetCaptureMode( - AllocationContextTracker::CaptureMode::NATIVE_STACK); -#endif -#if BUILDFLAG(ENABLE_MEMORY_TASK_PROFILER) - } else if (profiling_mode == switches::kEnableHeapProfilingTaskProfiler) { - // Enable heap tracking, which in turn enables capture of heap usage - // tracking in tracked_objects.cc. - if (!base::debug::ThreadHeapUsageTracker::IsHeapTrackingEnabled()) - base::debug::ThreadHeapUsageTracker::EnableHeapTracking(); -#endif - } else { - CHECK(false) << "Invalid mode '" << profiling_mode << "' for " - << switches::kEnableHeapProfiling << " flag."; - } - - for (auto mdp : dump_providers_) - mdp->dump_provider->OnHeapProfilingEnabled(true); - heap_profiling_enabled_ = true; -} - -void MemoryDumpManager::Initialize( - std::unique_ptr delegate) { - { - AutoLock lock(lock_); - DCHECK(delegate); - DCHECK(!delegate_); - delegate_ = std::move(delegate); - EnableHeapProfilingIfNeeded(); - } - -// Enable the core dump providers. -#if defined(MALLOC_MEMORY_TRACING_SUPPORTED) - RegisterDumpProvider(MallocDumpProvider::GetInstance(), "Malloc", nullptr); -#endif - -#if defined(OS_ANDROID) - RegisterDumpProvider(JavaHeapDumpProvider::GetInstance(), "JavaHeap", - nullptr); -#endif - - TRACE_EVENT_WARMUP_CATEGORY(kTraceCategory); - - // TODO(ssid): This should be done in EnableHeapProfiling so that we capture - // more allocations (crbug.com/625170). - if (AllocationContextTracker::capture_mode() == - AllocationContextTracker::CaptureMode::PSEUDO_STACK && - !(TraceLog::GetInstance()->enabled_modes() & TraceLog::FILTERING_MODE)) { - // Create trace config with heap profiling filter. - std::string filter_string = "*"; - const char* const kFilteredCategories[] = { - TRACE_DISABLED_BY_DEFAULT("net"), TRACE_DISABLED_BY_DEFAULT("cc"), - MemoryDumpManager::kTraceCategory}; - for (const char* cat : kFilteredCategories) - filter_string = filter_string + "," + cat; - TraceConfigCategoryFilter category_filter; - category_filter.InitializeFromString(filter_string); - - TraceConfig::EventFilterConfig heap_profiler_filter_config( - HeapProfilerEventFilter::kName); - heap_profiler_filter_config.SetCategoryFilter(category_filter); - - TraceConfig::EventFilters filters; - filters.push_back(heap_profiler_filter_config); - TraceConfig filtering_trace_config; - filtering_trace_config.SetEventFilters(filters); - - TraceLog::GetInstance()->SetEnabled(filtering_trace_config, - TraceLog::FILTERING_MODE); - } - - // If tracing was enabled before initializing MemoryDumpManager, we missed the - // OnTraceLogEnabled() event. Synthetize it so we can late-join the party. - // IsEnabled is called before adding observer to avoid calling - // OnTraceLogEnabled twice. - bool is_tracing_already_enabled = TraceLog::GetInstance()->IsEnabled(); - TraceLog::GetInstance()->AddEnabledStateObserver(this); - if (is_tracing_already_enabled) - OnTraceLogEnabled(); -} - -void MemoryDumpManager::RegisterDumpProvider( - MemoryDumpProvider* mdp, - const char* name, - scoped_refptr task_runner, - MemoryDumpProvider::Options options) { - options.dumps_on_single_thread_task_runner = true; - RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options); -} - -void MemoryDumpManager::RegisterDumpProvider( - MemoryDumpProvider* mdp, - const char* name, - scoped_refptr task_runner) { - // Set |dumps_on_single_thread_task_runner| to true because all providers - // without task runner are run on dump thread. - MemoryDumpProvider::Options options; - options.dumps_on_single_thread_task_runner = true; - RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options); -} - -void MemoryDumpManager::RegisterDumpProviderWithSequencedTaskRunner( - MemoryDumpProvider* mdp, - const char* name, - scoped_refptr task_runner, - MemoryDumpProvider::Options options) { - DCHECK(task_runner); - options.dumps_on_single_thread_task_runner = false; - RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options); -} - -void MemoryDumpManager::RegisterDumpProviderInternal( - MemoryDumpProvider* mdp, - const char* name, - scoped_refptr task_runner, - const MemoryDumpProvider::Options& options) { - if (dumper_registrations_ignored_for_testing_) - return; - - bool whitelisted_for_background_mode = IsMemoryDumpProviderWhitelisted(name); - scoped_refptr mdpinfo = - new MemoryDumpProviderInfo(mdp, name, std::move(task_runner), options, - whitelisted_for_background_mode); - - if (options.is_fast_polling_supported) { - DCHECK(!mdpinfo->task_runner) << "MemoryDumpProviders capable of fast " - "polling must NOT be thread bound."; - } - - { - AutoLock lock(lock_); - bool already_registered = !dump_providers_.insert(mdpinfo).second; - // This actually happens in some tests which don't have a clean tear-down - // path for RenderThreadImpl::Init(). - if (already_registered) - return; - - // The list of polling MDPs is populated OnTraceLogEnabled(). This code - // deals with the case of a MDP capable of fast polling that is registered - // after the OnTraceLogEnabled() - if (options.is_fast_polling_supported && dump_thread_) { - dump_thread_->task_runner()->PostTask( - FROM_HERE, Bind(&MemoryDumpManager::RegisterPollingMDPOnDumpThread, - Unretained(this), mdpinfo)); - } - } - - if (heap_profiling_enabled_) - mdp->OnHeapProfilingEnabled(true); -} - -void MemoryDumpManager::UnregisterDumpProvider(MemoryDumpProvider* mdp) { - UnregisterDumpProviderInternal(mdp, false /* delete_async */); -} - -void MemoryDumpManager::UnregisterAndDeleteDumpProviderSoon( - std::unique_ptr mdp) { - UnregisterDumpProviderInternal(mdp.release(), true /* delete_async */); -} - -void MemoryDumpManager::UnregisterDumpProviderInternal( - MemoryDumpProvider* mdp, - bool take_mdp_ownership_and_delete_async) { - std::unique_ptr owned_mdp; - if (take_mdp_ownership_and_delete_async) - owned_mdp.reset(mdp); - - AutoLock lock(lock_); - - auto mdp_iter = dump_providers_.begin(); - for (; mdp_iter != dump_providers_.end(); ++mdp_iter) { - if ((*mdp_iter)->dump_provider == mdp) - break; - } - - if (mdp_iter == dump_providers_.end()) - return; // Not registered / already unregistered. - - if (take_mdp_ownership_and_delete_async) { - // The MDP will be deleted whenever the MDPInfo struct will, that is either: - // - At the end of this function, if no dump is in progress. - // - Either in SetupNextMemoryDump() or InvokeOnMemoryDump() when MDPInfo is - // removed from |pending_dump_providers|. - // - When the provider is removed from |dump_providers_for_polling_|. - DCHECK(!(*mdp_iter)->owned_dump_provider); - (*mdp_iter)->owned_dump_provider = std::move(owned_mdp); - } else if (strict_thread_check_blacklist_.count((*mdp_iter)->name) == 0 || - subtle::NoBarrier_Load(&memory_tracing_enabled_)) { - // If dump provider's name is on |strict_thread_check_blacklist_|, then the - // DCHECK is fired only when tracing is enabled. Otherwise the DCHECK is - // fired even when tracing is not enabled (stricter). - // TODO(ssid): Remove this condition after removing all the dump providers - // in the blacklist and the buildbots are no longer flakily hitting the - // DCHECK, crbug.com/643438. - - // If you hit this DCHECK, your dump provider has a bug. - // Unregistration of a MemoryDumpProvider is safe only if: - // - The MDP has specified a sequenced task runner affinity AND the - // unregistration happens on the same task runner. So that the MDP cannot - // unregister and be in the middle of a OnMemoryDump() at the same time. - // - The MDP has NOT specified a task runner affinity and its ownership is - // transferred via UnregisterAndDeleteDumpProviderSoon(). - // In all the other cases, it is not possible to guarantee that the - // unregistration will not race with OnMemoryDump() calls. - DCHECK((*mdp_iter)->task_runner && - (*mdp_iter)->task_runner->RunsTasksOnCurrentThread()) - << "MemoryDumpProvider \"" << (*mdp_iter)->name << "\" attempted to " - << "unregister itself in a racy way. Please file a crbug."; - } - - if ((*mdp_iter)->options.is_fast_polling_supported && dump_thread_) { - DCHECK(take_mdp_ownership_and_delete_async); - dump_thread_->task_runner()->PostTask( - FROM_HERE, Bind(&MemoryDumpManager::UnregisterPollingMDPOnDumpThread, - Unretained(this), *mdp_iter)); - } - - // The MDPInfo instance can still be referenced by the - // |ProcessMemoryDumpAsyncState.pending_dump_providers|. For this reason - // the MDPInfo is flagged as disabled. It will cause InvokeOnMemoryDump() - // to just skip it, without actually invoking the |mdp|, which might be - // destroyed by the caller soon after this method returns. - (*mdp_iter)->disabled = true; - dump_providers_.erase(mdp_iter); -} - -void MemoryDumpManager::RegisterPollingMDPOnDumpThread( - scoped_refptr mdpinfo) { - AutoLock lock(lock_); - dump_providers_for_polling_.insert(mdpinfo); - - // Notify ready for polling when first polling supported provider is - // registered. This handles the case where OnTraceLogEnabled() did not notify - // ready since no polling supported mdp has yet been registered. - if (dump_providers_for_polling_.size() == 1) - MemoryDumpScheduler::GetInstance()->EnablePollingIfNeeded(); -} - -void MemoryDumpManager::UnregisterPollingMDPOnDumpThread( - scoped_refptr mdpinfo) { - mdpinfo->dump_provider->SuspendFastMemoryPolling(); - - AutoLock lock(lock_); - dump_providers_for_polling_.erase(mdpinfo); - DCHECK(!dump_providers_for_polling_.empty()) - << "All polling MDPs cannot be unregistered."; -} - -void MemoryDumpManager::RequestGlobalDump( - MemoryDumpType dump_type, - MemoryDumpLevelOfDetail level_of_detail, - const MemoryDumpCallback& callback) { - // Bail out immediately if tracing is not enabled at all or if the dump mode - // is not allowed. - if (!UNLIKELY(subtle::NoBarrier_Load(&memory_tracing_enabled_)) || - !IsDumpModeAllowed(level_of_detail)) { - VLOG(1) << kLogPrefix << " failed because " << kTraceCategory - << " tracing category is not enabled or the requested dump mode is " - "not allowed by trace config."; - if (!callback.is_null()) - callback.Run(0u /* guid */, false /* success */); - return; - } - - const uint64_t guid = - TraceLog::GetInstance()->MangleEventId(g_next_guid.GetNext()); - - // Creates an async event to keep track of the global dump evolution. - // The |wrapped_callback| will generate the ASYNC_END event and then invoke - // the real |callback| provided by the caller. - TRACE_EVENT_NESTABLE_ASYNC_BEGIN2( - kTraceCategory, "GlobalMemoryDump", TRACE_ID_LOCAL(guid), "dump_type", - MemoryDumpTypeToString(dump_type), "level_of_detail", - MemoryDumpLevelOfDetailToString(level_of_detail)); - MemoryDumpCallback wrapped_callback = Bind(&OnGlobalDumpDone, callback); - - // The delegate will coordinate the IPC broadcast and at some point invoke - // CreateProcessDump() to get a dump for the current process. - MemoryDumpRequestArgs args = {guid, dump_type, level_of_detail}; - delegate_->RequestGlobalMemoryDump(args, wrapped_callback); -} - -void MemoryDumpManager::RequestGlobalDump( - MemoryDumpType dump_type, - MemoryDumpLevelOfDetail level_of_detail) { - RequestGlobalDump(dump_type, level_of_detail, MemoryDumpCallback()); -} - -bool MemoryDumpManager::IsDumpProviderRegisteredForTesting( - MemoryDumpProvider* provider) { - AutoLock lock(lock_); - - for (const auto& info : dump_providers_) { - if (info->dump_provider == provider) - return true; - } - return false; -} - -void MemoryDumpManager::CreateProcessDump(const MemoryDumpRequestArgs& args, - const MemoryDumpCallback& callback) { - char guid_str[20]; - sprintf(guid_str, "0x%" PRIx64, args.dump_guid); - TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(kTraceCategory, "ProcessMemoryDump", - TRACE_ID_LOCAL(args.dump_guid), "dump_guid", - TRACE_STR_COPY(guid_str)); - - // If argument filter is enabled then only background mode dumps should be - // allowed. In case the trace config passed for background tracing session - // missed the allowed modes argument, it crashes here instead of creating - // unexpected dumps. - if (TraceLog::GetInstance() - ->GetCurrentTraceConfig() - .IsArgumentFilterEnabled()) { - CHECK_EQ(MemoryDumpLevelOfDetail::BACKGROUND, args.level_of_detail); - } - - std::unique_ptr pmd_async_state; - { - AutoLock lock(lock_); - - // |dump_thread_| can be nullptr is tracing was disabled before reaching - // here. SetupNextMemoryDump() is robust enough to tolerate it and will - // NACK the dump. - pmd_async_state.reset(new ProcessMemoryDumpAsyncState( - args, dump_providers_, session_state_, callback, - dump_thread_ ? dump_thread_->task_runner() : nullptr)); - - // Safety check to prevent reaching here without calling RequestGlobalDump, - // with disallowed modes. If |session_state_| is null then tracing is - // disabled. - CHECK(!session_state_ || - session_state_->IsDumpModeAllowed(args.level_of_detail)); - - MemoryDumpScheduler::GetInstance()->NotifyDumpTriggered(); - } - - // Start the process dump. This involves task runner hops as specified by the - // MemoryDumpProvider(s) in RegisterDumpProvider()). - SetupNextMemoryDump(std::move(pmd_async_state)); -} - -// PostTask InvokeOnMemoryDump() to the dump provider's sequenced task runner. A -// PostTask is always required for a generic SequencedTaskRunner to ensure that -// no other task is running on it concurrently. SetupNextMemoryDump() and -// InvokeOnMemoryDump() are called alternatively which linearizes the dump -// provider's OnMemoryDump invocations. -// At most one of either SetupNextMemoryDump() or InvokeOnMemoryDump() can be -// active at any time for a given PMD, regardless of status of the |lock_|. -// |lock_| is used in these functions purely to ensure consistency w.r.t. -// (un)registrations of |dump_providers_|. -void MemoryDumpManager::SetupNextMemoryDump( - std::unique_ptr pmd_async_state) { - HEAP_PROFILER_SCOPED_IGNORE; - // Initalizes the ThreadLocalEventBuffer to guarantee that the TRACE_EVENTs - // in the PostTask below don't end up registering their own dump providers - // (for discounting trace memory overhead) while holding the |lock_|. - TraceLog::GetInstance()->InitializeThreadLocalEventBufferIfSupported(); - - // |dump_thread_| might be destroyed before getting this point. - // It means that tracing was disabled right before starting this dump. - // Anyway either tracing is stopped or this was the last hop, create a trace - // event, add it to the trace and finalize process dump invoking the callback. - if (!pmd_async_state->dump_thread_task_runner.get()) { - if (pmd_async_state->pending_dump_providers.empty()) { - VLOG(1) << kLogPrefix << " failed because dump thread was destroyed" - << " before finalizing the dump"; - } else { - VLOG(1) << kLogPrefix << " failed because dump thread was destroyed" - << " before dumping " - << pmd_async_state->pending_dump_providers.back().get()->name; - } - pmd_async_state->dump_successful = false; - pmd_async_state->pending_dump_providers.clear(); - } - if (pmd_async_state->pending_dump_providers.empty()) - return FinalizeDumpAndAddToTrace(std::move(pmd_async_state)); - - // Read MemoryDumpProviderInfo thread safety considerations in - // memory_dump_manager.h when accessing |mdpinfo| fields. - MemoryDumpProviderInfo* mdpinfo = - pmd_async_state->pending_dump_providers.back().get(); - - // If we are in background tracing, we should invoke only the whitelisted - // providers. Ignore other providers and continue. - if (pmd_async_state->req_args.level_of_detail == - MemoryDumpLevelOfDetail::BACKGROUND && - !mdpinfo->whitelisted_for_background_mode) { - pmd_async_state->pending_dump_providers.pop_back(); - return SetupNextMemoryDump(std::move(pmd_async_state)); - } - - // If the dump provider did not specify a task runner affinity, dump on - // |dump_thread_| which is already checked above for presence. - SequencedTaskRunner* task_runner = mdpinfo->task_runner.get(); - if (!task_runner) { - DCHECK(mdpinfo->options.dumps_on_single_thread_task_runner); - task_runner = pmd_async_state->dump_thread_task_runner.get(); - DCHECK(task_runner); - } - - if (mdpinfo->options.dumps_on_single_thread_task_runner && - task_runner->RunsTasksOnCurrentThread()) { - // If |dumps_on_single_thread_task_runner| is true then no PostTask is - // required if we are on the right thread. - return InvokeOnMemoryDump(pmd_async_state.release()); - } - - bool did_post_task = task_runner->PostTask( - FROM_HERE, Bind(&MemoryDumpManager::InvokeOnMemoryDump, Unretained(this), - Unretained(pmd_async_state.get()))); - - if (did_post_task) { - // Ownership is tranferred to InvokeOnMemoryDump(). - ignore_result(pmd_async_state.release()); - return; - } - - // PostTask usually fails only if the process or thread is shut down. So, the - // dump provider is disabled here. But, don't disable unbound dump providers. - // The utility thread is normally shutdown when disabling the trace and - // getting here in this case is expected. - if (mdpinfo->task_runner) { - LOG(ERROR) << "Disabling MemoryDumpProvider \"" << mdpinfo->name - << "\". Failed to post task on the task runner provided."; - - // A locked access is required to R/W |disabled| (for the - // UnregisterAndDeleteDumpProviderSoon() case). - AutoLock lock(lock_); - mdpinfo->disabled = true; - } - - // PostTask failed. Ignore the dump provider and continue. - pmd_async_state->pending_dump_providers.pop_back(); - SetupNextMemoryDump(std::move(pmd_async_state)); -} - -// This function is called on the right task runner for current MDP. It is -// either the task runner specified by MDP or |dump_thread_task_runner| if the -// MDP did not specify task runner. Invokes the dump provider's OnMemoryDump() -// (unless disabled). -void MemoryDumpManager::InvokeOnMemoryDump( - ProcessMemoryDumpAsyncState* owned_pmd_async_state) { - HEAP_PROFILER_SCOPED_IGNORE; - // In theory |owned_pmd_async_state| should be a scoped_ptr. The only reason - // why it isn't is because of the corner case logic of |did_post_task| - // above, which needs to take back the ownership of the |pmd_async_state| when - // the PostTask() fails. - // Unfortunately, PostTask() destroys the scoped_ptr arguments upon failure - // to prevent accidental leaks. Using a scoped_ptr would prevent us to to - // skip the hop and move on. Hence the manual naked -> scoped ptr juggling. - auto pmd_async_state = WrapUnique(owned_pmd_async_state); - owned_pmd_async_state = nullptr; - - // Read MemoryDumpProviderInfo thread safety considerations in - // memory_dump_manager.h when accessing |mdpinfo| fields. - MemoryDumpProviderInfo* mdpinfo = - pmd_async_state->pending_dump_providers.back().get(); - - DCHECK(!mdpinfo->task_runner || - mdpinfo->task_runner->RunsTasksOnCurrentThread()); - - bool should_dump; - { - // A locked access is required to R/W |disabled| (for the - // UnregisterAndDeleteDumpProviderSoon() case). - AutoLock lock(lock_); - - // Unregister the dump provider if it failed too many times consecutively. - if (!mdpinfo->disabled && - mdpinfo->consecutive_failures >= kMaxConsecutiveFailuresCount) { - mdpinfo->disabled = true; - LOG(ERROR) << "Disabling MemoryDumpProvider \"" << mdpinfo->name - << "\". Dump failed multiple times consecutively."; - } - should_dump = !mdpinfo->disabled; - } // AutoLock lock(lock_); - - if (should_dump) { - // Invoke the dump provider. - TRACE_EVENT1(kTraceCategory, "MemoryDumpManager::InvokeOnMemoryDump", - "dump_provider.name", mdpinfo->name); - - // A stack allocated string with dump provider name is useful to debug - // crashes while invoking dump after a |dump_provider| is not unregistered - // in safe way. - // TODO(ssid): Remove this after fixing crbug.com/643438. - char provider_name_for_debugging[16]; - strncpy(provider_name_for_debugging, mdpinfo->name, - sizeof(provider_name_for_debugging) - 1); - provider_name_for_debugging[sizeof(provider_name_for_debugging) - 1] = '\0'; - base::debug::Alias(provider_name_for_debugging); - - // Pid of the target process being dumped. Often kNullProcessId (= current - // process), non-zero when the coordinator process creates dumps on behalf - // of child processes (see crbug.com/461788). - ProcessId target_pid = mdpinfo->options.target_pid; - MemoryDumpArgs args = {pmd_async_state->req_args.level_of_detail}; - ProcessMemoryDump* pmd = - pmd_async_state->GetOrCreateMemoryDumpContainerForProcess(target_pid, - args); - bool dump_successful = mdpinfo->dump_provider->OnMemoryDump(args, pmd); - mdpinfo->consecutive_failures = - dump_successful ? 0 : mdpinfo->consecutive_failures + 1; - } - - pmd_async_state->pending_dump_providers.pop_back(); - SetupNextMemoryDump(std::move(pmd_async_state)); -} - -bool MemoryDumpManager::PollFastMemoryTotal(uint64_t* memory_total) { -#if DCHECK_IS_ON() - { - AutoLock lock(lock_); - if (dump_thread_) - DCHECK(dump_thread_->task_runner()->BelongsToCurrentThread()); - } -#endif - if (dump_providers_for_polling_.empty()) - return false; - - *memory_total = 0; - // Note that we call PollFastMemoryTotal() even if the dump provider is - // disabled (unregistered). This is to avoid taking lock while polling. - for (const auto& mdpinfo : dump_providers_for_polling_) { - uint64_t value = 0; - mdpinfo->dump_provider->PollFastMemoryTotal(&value); - *memory_total += value; - } - return true; -} - -// static -uint32_t MemoryDumpManager::GetDumpsSumKb(const std::string& pattern, - const ProcessMemoryDump* pmd) { - uint64_t sum = 0; - for (const auto& kv : pmd->allocator_dumps()) { - auto name = StringPiece(kv.first); - if (MatchPattern(name, pattern)) - sum += kv.second->GetSize(); - } - return sum / 1024; -} - -// static -void MemoryDumpManager::FinalizeDumpAndAddToTrace( - std::unique_ptr pmd_async_state) { - HEAP_PROFILER_SCOPED_IGNORE; - DCHECK(pmd_async_state->pending_dump_providers.empty()); - const uint64_t dump_guid = pmd_async_state->req_args.dump_guid; - if (!pmd_async_state->callback_task_runner->BelongsToCurrentThread()) { - scoped_refptr callback_task_runner = - pmd_async_state->callback_task_runner; - callback_task_runner->PostTask( - FROM_HERE, Bind(&MemoryDumpManager::FinalizeDumpAndAddToTrace, - Passed(&pmd_async_state))); - return; - } - - TRACE_EVENT0(kTraceCategory, "MemoryDumpManager::FinalizeDumpAndAddToTrace"); - - // The results struct to fill. - // TODO(hjd): Transitional until we send the full PMD. See crbug.com/704203 - MemoryDumpCallbackResult result; - - for (const auto& kv : pmd_async_state->process_dumps) { - ProcessId pid = kv.first; // kNullProcessId for the current process. - ProcessMemoryDump* process_memory_dump = kv.second.get(); - std::unique_ptr traced_value(new TracedValue); - process_memory_dump->AsValueInto(traced_value.get()); - traced_value->SetString("level_of_detail", - MemoryDumpLevelOfDetailToString( - pmd_async_state->req_args.level_of_detail)); - const char* const event_name = - MemoryDumpTypeToString(pmd_async_state->req_args.dump_type); - - std::unique_ptr event_value( - std::move(traced_value)); - TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_PROCESS_ID( - TRACE_EVENT_PHASE_MEMORY_DUMP, - TraceLog::GetCategoryGroupEnabled(kTraceCategory), event_name, - trace_event_internal::kGlobalScope, dump_guid, pid, - kTraceEventNumArgs, kTraceEventArgNames, - kTraceEventArgTypes, nullptr /* arg_values */, &event_value, - TRACE_EVENT_FLAG_HAS_ID); - - // TODO(hjd): Transitional until we send the full PMD. See crbug.com/704203 - // Don't try to fill the struct in detailed mode since it is hard to avoid - // double counting. - if (pmd_async_state->req_args.level_of_detail == - MemoryDumpLevelOfDetail::DETAILED) - continue; - - // TODO(hjd): Transitional until we send the full PMD. See crbug.com/704203 - if (pid == kNullProcessId) { - result.chrome_dump.malloc_total_kb = - GetDumpsSumKb("malloc", process_memory_dump); - result.chrome_dump.v8_total_kb = - GetDumpsSumKb("v8/*", process_memory_dump); - - // partition_alloc reports sizes for both allocated_objects and - // partitions. The memory allocated_objects uses is a subset of - // the partitions memory so to avoid double counting we only - // count partitions memory. - result.chrome_dump.partition_alloc_total_kb = - GetDumpsSumKb("partition_alloc/partitions/*", process_memory_dump); - result.chrome_dump.blink_gc_total_kb = - GetDumpsSumKb("blink_gc", process_memory_dump); - } - } - - bool tracing_still_enabled; - TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTraceCategory, &tracing_still_enabled); - if (!tracing_still_enabled) { - pmd_async_state->dump_successful = false; - VLOG(1) << kLogPrefix << " failed because tracing was disabled before" - << " the dump was completed"; - } - - if (!pmd_async_state->callback.is_null()) { - pmd_async_state->callback.Run(dump_guid, pmd_async_state->dump_successful); - pmd_async_state->callback.Reset(); - } - - TRACE_EVENT_NESTABLE_ASYNC_END0(kTraceCategory, "ProcessMemoryDump", - TRACE_ID_LOCAL(dump_guid)); -} - -void MemoryDumpManager::OnTraceLogEnabled() { - bool enabled; - TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTraceCategory, &enabled); - if (!enabled) - return; - - // Initialize the TraceLog for the current thread. This is to avoid that the - // TraceLog memory dump provider is registered lazily in the PostTask() below - // while the |lock_| is taken; - TraceLog::GetInstance()->InitializeThreadLocalEventBufferIfSupported(); - - // Spin-up the thread used to invoke unbound dump providers. - std::unique_ptr dump_thread(new Thread("MemoryInfra")); - if (!dump_thread->Start()) { - LOG(ERROR) << "Failed to start the memory-infra thread for tracing"; - return; - } - - const TraceConfig& trace_config = - TraceLog::GetInstance()->GetCurrentTraceConfig(); - const TraceConfig::MemoryDumpConfig& memory_dump_config = - trace_config.memory_dump_config(); - scoped_refptr session_state = - new MemoryDumpSessionState; - session_state->SetAllowedDumpModes(memory_dump_config.allowed_dump_modes); - session_state->set_heap_profiler_breakdown_threshold_bytes( - memory_dump_config.heap_profiler_options.breakdown_threshold_bytes); - if (heap_profiling_enabled_) { - // If heap profiling is enabled, the stack frame deduplicator and type name - // deduplicator will be in use. Add a metadata events to write the frames - // and type IDs. - session_state->SetStackFrameDeduplicator( - WrapUnique(new StackFrameDeduplicator)); - - session_state->SetTypeNameDeduplicator( - WrapUnique(new TypeNameDeduplicator)); - - TRACE_EVENT_API_ADD_METADATA_EVENT( - TraceLog::GetCategoryGroupEnabled("__metadata"), "stackFrames", - "stackFrames", - MakeUnique>( - session_state, &MemoryDumpSessionState::stack_frame_deduplicator)); - - TRACE_EVENT_API_ADD_METADATA_EVENT( - TraceLog::GetCategoryGroupEnabled("__metadata"), "typeNames", - "typeNames", - MakeUnique>( - session_state, &MemoryDumpSessionState::type_name_deduplicator)); - } - - { - AutoLock lock(lock_); - - DCHECK(delegate_); // At this point we must have a delegate. - session_state_ = session_state; - - DCHECK(!dump_thread_); - dump_thread_ = std::move(dump_thread); - - subtle::NoBarrier_Store(&memory_tracing_enabled_, 1); - - dump_providers_for_polling_.clear(); - for (const auto& mdpinfo : dump_providers_) { - if (mdpinfo->options.is_fast_polling_supported) - dump_providers_for_polling_.insert(mdpinfo); - } - - MemoryDumpScheduler* dump_scheduler = MemoryDumpScheduler::GetInstance(); - dump_scheduler->Setup(this, dump_thread_->task_runner()); - DCHECK_LE(memory_dump_config.triggers.size(), 3u); - for (const auto& trigger : memory_dump_config.triggers) { - if (!session_state_->IsDumpModeAllowed(trigger.level_of_detail)) { - NOTREACHED(); - continue; - } - dump_scheduler->AddTrigger(trigger.trigger_type, trigger.level_of_detail, - trigger.min_time_between_dumps_ms); - } - - // Notify polling supported only if some polling supported provider was - // registered, else RegisterPollingMDPOnDumpThread() will notify when first - // polling MDP registers. - if (!dump_providers_for_polling_.empty()) - dump_scheduler->EnablePollingIfNeeded(); - - // Only coordinator process triggers periodic global memory dumps. - if (delegate_->IsCoordinator()) - dump_scheduler->EnablePeriodicTriggerIfNeeded(); - } - -} - -void MemoryDumpManager::OnTraceLogDisabled() { - // There might be a memory dump in progress while this happens. Therefore, - // ensure that the MDM state which depends on the tracing enabled / disabled - // state is always accessed by the dumping methods holding the |lock_|. - if (!subtle::NoBarrier_Load(&memory_tracing_enabled_)) - return; - subtle::NoBarrier_Store(&memory_tracing_enabled_, 0); - std::unique_ptr dump_thread; - { - AutoLock lock(lock_); - dump_thread = std::move(dump_thread_); - session_state_ = nullptr; - MemoryDumpScheduler::GetInstance()->DisableAllTriggers(); - } - - // Thread stops are blocking and must be performed outside of the |lock_| - // or will deadlock (e.g., if SetupNextMemoryDump() tries to acquire it). - if (dump_thread) - dump_thread->Stop(); - - // |dump_providers_for_polling_| must be cleared only after the dump thread is - // stopped (polling tasks are done). - { - AutoLock lock(lock_); - for (const auto& mdpinfo : dump_providers_for_polling_) - mdpinfo->dump_provider->SuspendFastMemoryPolling(); - dump_providers_for_polling_.clear(); - } -} - -bool MemoryDumpManager::IsDumpModeAllowed(MemoryDumpLevelOfDetail dump_mode) { - AutoLock lock(lock_); - if (!session_state_) - return false; - return session_state_->IsDumpModeAllowed(dump_mode); -} - -MemoryDumpManager::ProcessMemoryDumpAsyncState::ProcessMemoryDumpAsyncState( - MemoryDumpRequestArgs req_args, - const MemoryDumpProviderInfo::OrderedSet& dump_providers, - scoped_refptr session_state, - MemoryDumpCallback callback, - scoped_refptr dump_thread_task_runner) - : req_args(req_args), - session_state(std::move(session_state)), - callback(callback), - dump_successful(true), - callback_task_runner(ThreadTaskRunnerHandle::Get()), - dump_thread_task_runner(std::move(dump_thread_task_runner)) { - pending_dump_providers.reserve(dump_providers.size()); - pending_dump_providers.assign(dump_providers.rbegin(), dump_providers.rend()); -} - -MemoryDumpManager::ProcessMemoryDumpAsyncState::~ProcessMemoryDumpAsyncState() { -} - -ProcessMemoryDump* MemoryDumpManager::ProcessMemoryDumpAsyncState:: - GetOrCreateMemoryDumpContainerForProcess(ProcessId pid, - const MemoryDumpArgs& dump_args) { - auto iter = process_dumps.find(pid); - if (iter == process_dumps.end()) { - std::unique_ptr new_pmd( - new ProcessMemoryDump(session_state, dump_args)); - iter = process_dumps.insert(std::make_pair(pid, std::move(new_pmd))).first; - } - return iter->second.get(); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_dump_manager.h b/base/trace_event/memory_dump_manager.h deleted file mode 100644 index e7f51948506f2458232fd62b1840b50062ce8ba4..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_manager.h +++ /dev/null @@ -1,354 +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. - -#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_MANAGER_H_ -#define BASE_TRACE_EVENT_MEMORY_DUMP_MANAGER_H_ - -#include - -#include -#include -#include -#include - -#include "base/atomicops.h" -#include "base/containers/hash_tables.h" -#include "base/macros.h" -#include "base/memory/ref_counted.h" -#include "base/memory/singleton.h" -#include "base/synchronization/lock.h" -#include "base/trace_event/memory_allocator_dump.h" -#include "base/trace_event/memory_dump_provider_info.h" -#include "base/trace_event/memory_dump_request_args.h" -#include "base/trace_event/process_memory_dump.h" -#include "base/trace_event/trace_event.h" - -// Forward declare |MemoryDumpManagerDelegateImplTest| so that we can make it a -// friend of |MemoryDumpManager| and give it access to |SetInstanceForTesting|. -namespace memory_instrumentation { - -class MemoryDumpManagerDelegateImplTest; - -} // namespace memory_instrumentation - -namespace base { - -class SingleThreadTaskRunner; -class Thread; - -namespace trace_event { - -class MemoryDumpManagerDelegate; -class MemoryDumpProvider; -class MemoryDumpSessionState; -class MemoryDumpScheduler; - -// This is the interface exposed to the rest of the codebase to deal with -// memory tracing. The main entry point for clients is represented by -// RequestDumpPoint(). The extension by Un(RegisterDumpProvider). -class BASE_EXPORT MemoryDumpManager : public TraceLog::EnabledStateObserver { - public: - static const char* const kTraceCategory; - static const char* const kLogPrefix; - - // This value is returned as the tracing id of the child processes by - // GetTracingProcessId() when tracing is not enabled. - static const uint64_t kInvalidTracingProcessId; - - static MemoryDumpManager* GetInstance(); - - // Invoked once per process to listen to trace begin / end events. - // Initialization can happen after (Un)RegisterMemoryDumpProvider() calls - // and the MemoryDumpManager guarantees to support this. - // On the other side, the MemoryDumpManager will not be fully operational - // (i.e. will NACK any RequestGlobalMemoryDump()) until initialized. - // Arguments: - // delegate: inversion-of-control interface for embedder-specific behaviors - // (multiprocess handshaking). See the lifetime and thread-safety - // requirements in the |MemoryDumpManagerDelegate| docstring. - void Initialize(std::unique_ptr delegate); - - // (Un)Registers a MemoryDumpProvider instance. - // Args: - // - mdp: the MemoryDumpProvider instance to be registered. MemoryDumpManager - // does NOT take memory ownership of |mdp|, which is expected to either - // be a singleton or unregister itself. - // - name: a friendly name (duplicates allowed). Used for debugging and - // run-time profiling of memory-infra internals. Must be a long-lived - // C string. - // - task_runner: either a SingleThreadTaskRunner or SequencedTaskRunner. All - // the calls to |mdp| will be run on the given |task_runner|. If passed - // null |mdp| should be able to handle calls on arbitrary threads. - // - options: extra optional arguments. See memory_dump_provider.h. - void RegisterDumpProvider(MemoryDumpProvider* mdp, - const char* name, - scoped_refptr task_runner); - void RegisterDumpProvider(MemoryDumpProvider* mdp, - const char* name, - scoped_refptr task_runner, - MemoryDumpProvider::Options options); - void RegisterDumpProviderWithSequencedTaskRunner( - MemoryDumpProvider* mdp, - const char* name, - scoped_refptr task_runner, - MemoryDumpProvider::Options options); - void UnregisterDumpProvider(MemoryDumpProvider* mdp); - - // Unregisters an unbound dump provider and takes care about its deletion - // asynchronously. Can be used only for for dump providers with no - // task-runner affinity. - // This method takes ownership of the dump provider and guarantees that: - // - The |mdp| will be deleted at some point in the near future. - // - Its deletion will not happen concurrently with the OnMemoryDump() call. - // Note that OnMemoryDump() and PollFastMemoryTotal() calls can still happen - // after this method returns. - void UnregisterAndDeleteDumpProviderSoon( - std::unique_ptr mdp); - - // Requests a memory dump. The dump might happen or not depending on the - // filters and categories specified when enabling tracing. - // The optional |callback| is executed asynchronously, on an arbitrary thread, - // to notify about the completion of the global dump (i.e. after all the - // processes have dumped) and its success (true iff all the dumps were - // successful). - void RequestGlobalDump(MemoryDumpType dump_type, - MemoryDumpLevelOfDetail level_of_detail, - const MemoryDumpCallback& callback); - - // Same as above (still asynchronous), but without callback. - void RequestGlobalDump(MemoryDumpType dump_type, - MemoryDumpLevelOfDetail level_of_detail); - - // TraceLog::EnabledStateObserver implementation. - void OnTraceLogEnabled() override; - void OnTraceLogDisabled() override; - - // Enable heap profiling if kEnableHeapProfiling is specified. - void EnableHeapProfilingIfNeeded(); - - // Returns true if the dump mode is allowed for current tracing session. - bool IsDumpModeAllowed(MemoryDumpLevelOfDetail dump_mode); - - // Lets tests see if a dump provider is registered. - bool IsDumpProviderRegisteredForTesting(MemoryDumpProvider*); - - // Returns the MemoryDumpSessionState object, which is shared by all the - // ProcessMemoryDump and MemoryAllocatorDump instances through all the tracing - // session lifetime. - const scoped_refptr& session_state_for_testing() - const { - return session_state_; - } - - // Returns a unique id for identifying the processes. The id can be - // retrieved by child processes only when tracing is enabled. This is - // intended to express cross-process sharing of memory dumps on the - // child-process side, without having to know its own child process id. - uint64_t GetTracingProcessId() const { return tracing_process_id_; } - void set_tracing_process_id(uint64_t tracing_process_id) { - tracing_process_id_ = tracing_process_id; - } - - // Returns the name for a the allocated_objects dump. Use this to declare - // suballocator dumps from other dump providers. - // It will return nullptr if there is no dump provider for the system - // allocator registered (which is currently the case for Mac OS). - const char* system_allocator_pool_name() const { - return kSystemAllocatorPoolName; - }; - - // When set to true, calling |RegisterMemoryDumpProvider| is a no-op. - void set_dumper_registrations_ignored_for_testing(bool ignored) { - dumper_registrations_ignored_for_testing_ = ignored; - } - - private: - friend std::default_delete; // For the testing instance. - friend struct DefaultSingletonTraits; - friend class MemoryDumpManagerDelegate; - friend class MemoryDumpManagerTest; - friend class MemoryDumpScheduler; - friend class memory_instrumentation::MemoryDumpManagerDelegateImplTest; - - // Holds the state of a process memory dump that needs to be carried over - // across task runners in order to fulfil an asynchronous CreateProcessDump() - // request. At any time exactly one task runner owns a - // ProcessMemoryDumpAsyncState. - struct ProcessMemoryDumpAsyncState { - ProcessMemoryDumpAsyncState( - MemoryDumpRequestArgs req_args, - const MemoryDumpProviderInfo::OrderedSet& dump_providers, - scoped_refptr session_state, - MemoryDumpCallback callback, - scoped_refptr dump_thread_task_runner); - ~ProcessMemoryDumpAsyncState(); - - // Gets or creates the memory dump container for the given target process. - ProcessMemoryDump* GetOrCreateMemoryDumpContainerForProcess( - ProcessId pid, - const MemoryDumpArgs& dump_args); - - // A map of ProcessId -> ProcessMemoryDump, one for each target process - // being dumped from the current process. Typically each process dumps only - // for itself, unless dump providers specify a different |target_process| in - // MemoryDumpProvider::Options. - std::map> process_dumps; - - // The arguments passed to the initial CreateProcessDump() request. - const MemoryDumpRequestArgs req_args; - - // An ordered sequence of dump providers that have to be invoked to complete - // the dump. This is a copy of |dump_providers_| at the beginning of a dump - // and becomes empty at the end, when all dump providers have been invoked. - std::vector> pending_dump_providers; - - // The trace-global session state. - scoped_refptr session_state; - - // Callback passed to the initial call to CreateProcessDump(). - MemoryDumpCallback callback; - - // The |success| field that will be passed as argument to the |callback|. - bool dump_successful; - - // The thread on which FinalizeDumpAndAddToTrace() (and hence |callback|) - // should be invoked. This is the thread on which the initial - // CreateProcessDump() request was called. - const scoped_refptr callback_task_runner; - - // The thread on which unbound dump providers should be invoked. - // This is essentially |dump_thread_|.task_runner() but needs to be kept - // as a separate variable as it needs to be accessed by arbitrary dumpers' - // threads outside of the lock_ to avoid races when disabling tracing. - // It is immutable for all the duration of a tracing session. - const scoped_refptr dump_thread_task_runner; - - private: - DISALLOW_COPY_AND_ASSIGN(ProcessMemoryDumpAsyncState); - }; - - static const int kMaxConsecutiveFailuresCount; - static const char* const kSystemAllocatorPoolName; - - MemoryDumpManager(); - ~MemoryDumpManager() override; - - static void SetInstanceForTesting(MemoryDumpManager* instance); - static uint32_t GetDumpsSumKb(const std::string&, const ProcessMemoryDump*); - static void FinalizeDumpAndAddToTrace( - std::unique_ptr pmd_async_state); - - // Internal, used only by MemoryDumpManagerDelegate. - // Creates a memory dump for the current process and appends it to the trace. - // |callback| will be invoked asynchronously upon completion on the same - // thread on which CreateProcessDump() was called. - void CreateProcessDump(const MemoryDumpRequestArgs& args, - const MemoryDumpCallback& callback); - - // Calls InvokeOnMemoryDump() for the next MDP on the task runner specified by - // the MDP while registration. On failure to do so, skips and continues to - // next MDP. - void SetupNextMemoryDump( - std::unique_ptr pmd_async_state); - - // Invokes OnMemoryDump() of the next MDP and calls SetupNextMemoryDump() at - // the end to continue the ProcessMemoryDump. Should be called on the MDP task - // runner. - void InvokeOnMemoryDump(ProcessMemoryDumpAsyncState* owned_pmd_async_state); - - // Records a quick total memory usage in |memory_total|. This is used to track - // and detect peaks in the memory usage of the process without having to - // record all data from dump providers. This value is approximate to trade-off - // speed, and not consistent with the rest of the memory-infra metrics. Must - // be called on the dump thread. - // Returns true if |memory_total| was updated by polling at least 1 MDP. - bool PollFastMemoryTotal(uint64_t* memory_total); - - // Helper for RegierDumpProvider* functions. - void RegisterDumpProviderInternal( - MemoryDumpProvider* mdp, - const char* name, - scoped_refptr task_runner, - const MemoryDumpProvider::Options& options); - - // Helper for the public UnregisterDumpProvider* functions. - void UnregisterDumpProviderInternal(MemoryDumpProvider* mdp, - bool take_mdp_ownership_and_delete_async); - - // Adds / removes provider that supports polling to - // |dump_providers_for_polling_|. - void RegisterPollingMDPOnDumpThread( - scoped_refptr mdpinfo); - void UnregisterPollingMDPOnDumpThread( - scoped_refptr mdpinfo); - - // An ordererd set of registered MemoryDumpProviderInfo(s), sorted by task - // runner affinity (MDPs belonging to the same task runners are adjacent). - MemoryDumpProviderInfo::OrderedSet dump_providers_; - - // A copy of mdpinfo list that support polling. It must be accessed only on - // the dump thread if dump thread exists. - MemoryDumpProviderInfo::OrderedSet dump_providers_for_polling_; - - // Shared among all the PMDs to keep state scoped to the tracing session. - scoped_refptr session_state_; - - // The list of names of dump providers that are blacklisted from strict thread - // affinity check on unregistration. - std::unordered_set - strict_thread_check_blacklist_; - - std::unique_ptr delegate_; - - // Protects from concurrent accesses to the |dump_providers_*| and |delegate_| - // to guard against disabling logging while dumping on another thread. - Lock lock_; - - // Optimization to avoid attempting any memory dump (i.e. to not walk an empty - // dump_providers_enabled_ list) when tracing is not enabled. - subtle::AtomicWord memory_tracing_enabled_; - - // Thread used for MemoryDumpProviders which don't specify a task runner - // affinity. - std::unique_ptr dump_thread_; - - // The unique id of the child process. This is created only for tracing and is - // expected to be valid only when tracing is enabled. - uint64_t tracing_process_id_; - - // When true, calling |RegisterMemoryDumpProvider| is a no-op. - bool dumper_registrations_ignored_for_testing_; - - // Whether new memory dump providers should be told to enable heap profiling. - bool heap_profiling_enabled_; - - DISALLOW_COPY_AND_ASSIGN(MemoryDumpManager); -}; - -// The delegate is supposed to be long lived (read: a Singleton) and thread -// safe (i.e. should expect calls from any thread and handle thread hopping). -class BASE_EXPORT MemoryDumpManagerDelegate { - public: - MemoryDumpManagerDelegate() {} - virtual ~MemoryDumpManagerDelegate() {} - - virtual void RequestGlobalMemoryDump(const MemoryDumpRequestArgs& args, - const MemoryDumpCallback& callback) = 0; - - virtual bool IsCoordinator() const = 0; - - protected: - void CreateProcessDump(const MemoryDumpRequestArgs& args, - const MemoryDumpCallback& callback) { - MemoryDumpManager::GetInstance()->CreateProcessDump(args, callback); - } - - private: - DISALLOW_COPY_AND_ASSIGN(MemoryDumpManagerDelegate); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_MEMORY_DUMP_MANAGER_H_ diff --git a/base/trace_event/memory_dump_manager_unittest.cc b/base/trace_event/memory_dump_manager_unittest.cc deleted file mode 100644 index e126edd3972e052f5ad6515fcc39741bf5daeac3..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_manager_unittest.cc +++ /dev/null @@ -1,1311 +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. - -#include "base/trace_event/memory_dump_manager.h" - -#include - -#include -#include -#include - -#include "base/bind_helpers.h" -#include "base/callback.h" -#include "base/memory/ptr_util.h" -#include "base/memory/ref_counted_memory.h" -#include "base/message_loop/message_loop.h" -#include "base/run_loop.h" -#include "base/strings/stringprintf.h" -#include "base/synchronization/waitable_event.h" -#include "base/test/sequenced_worker_pool_owner.h" -#include "base/test/test_io_thread.h" -#include "base/test/trace_event_analyzer.h" -#include "base/threading/platform_thread.h" -#include "base/threading/sequenced_task_runner_handle.h" -#include "base/threading/sequenced_worker_pool.h" -#include "base/threading/thread.h" -#include "base/threading/thread_task_runner_handle.h" -#include "base/trace_event/memory_dump_provider.h" -#include "base/trace_event/memory_dump_scheduler.h" -#include "base/trace_event/memory_infra_background_whitelist.h" -#include "base/trace_event/process_memory_dump.h" -#include "base/trace_event/trace_buffer.h" -#include "base/trace_event/trace_config_memory_test_util.h" -#include "build/build_config.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -using testing::_; -using testing::AnyNumber; -using testing::AtMost; -using testing::Between; -using testing::Invoke; -using testing::Return; - -namespace base { -namespace trace_event { - -// GTest matchers for MemoryDumpRequestArgs arguments. -MATCHER(IsDetailedDump, "") { - return arg.level_of_detail == MemoryDumpLevelOfDetail::DETAILED; -} - -MATCHER(IsLightDump, "") { - return arg.level_of_detail == MemoryDumpLevelOfDetail::LIGHT; -} - -MATCHER(IsBackgroundDump, "") { - return arg.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND; -} - -namespace { - -const char* kMDPName = "TestDumpProvider"; -const char* kWhitelistedMDPName = "WhitelistedTestDumpProvider"; -const char* const kTestMDPWhitelist[] = {kWhitelistedMDPName, nullptr}; - -void RegisterDumpProvider( - MemoryDumpProvider* mdp, - scoped_refptr task_runner, - const MemoryDumpProvider::Options& options, - const char* name = kMDPName) { - MemoryDumpManager* mdm = MemoryDumpManager::GetInstance(); - mdm->set_dumper_registrations_ignored_for_testing(false); - mdm->RegisterDumpProvider(mdp, name, std::move(task_runner), options); - mdm->set_dumper_registrations_ignored_for_testing(true); -} - -void RegisterDumpProvider( - MemoryDumpProvider* mdp, - scoped_refptr task_runner) { - RegisterDumpProvider(mdp, task_runner, MemoryDumpProvider::Options()); -} - -void RegisterDumpProviderWithSequencedTaskRunner( - MemoryDumpProvider* mdp, - scoped_refptr task_runner, - const MemoryDumpProvider::Options& options) { - MemoryDumpManager* mdm = MemoryDumpManager::GetInstance(); - mdm->set_dumper_registrations_ignored_for_testing(false); - mdm->RegisterDumpProviderWithSequencedTaskRunner(mdp, kMDPName, task_runner, - options); - mdm->set_dumper_registrations_ignored_for_testing(true); -} - -void OnTraceDataCollected(Closure quit_closure, - trace_event::TraceResultBuffer* buffer, - const scoped_refptr& json, - bool has_more_events) { - buffer->AddFragment(json->data()); - if (!has_more_events) - quit_closure.Run(); -} - -// Posts |task| to |task_runner| and blocks until it is executed. -void PostTaskAndWait(const tracked_objects::Location& from_here, - SequencedTaskRunner* task_runner, - base::OnceClosure task) { - base::WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL, - WaitableEvent::InitialState::NOT_SIGNALED); - task_runner->PostTask(from_here, std::move(task)); - task_runner->PostTask( - FROM_HERE, base::Bind(&WaitableEvent::Signal, base::Unretained(&event))); - // The SequencedTaskRunner guarantees that |event| will only be signaled after - // |task| is executed. - event.Wait(); -} - -// Testing MemoryDumpManagerDelegate which, by default, short-circuits dump -// requests locally to the MemoryDumpManager instead of performing IPC dances. -class MemoryDumpManagerDelegateForTesting : public MemoryDumpManagerDelegate { - public: - MemoryDumpManagerDelegateForTesting(bool is_coordinator) - : is_coordinator_(is_coordinator) { - ON_CALL(*this, RequestGlobalMemoryDump(_, _)) - .WillByDefault(Invoke( - this, &MemoryDumpManagerDelegateForTesting::CreateProcessDump)); - } - - MOCK_METHOD2(RequestGlobalMemoryDump, - void(const MemoryDumpRequestArgs& args, - const MemoryDumpCallback& callback)); - - bool IsCoordinator() const override { return is_coordinator_; } - - // Promote the CreateProcessDump to public so it can be used by test fixtures. - using MemoryDumpManagerDelegate::CreateProcessDump; - - private: - bool is_coordinator_; -}; - -class MockMemoryDumpProvider : public MemoryDumpProvider { - public: - MOCK_METHOD0(Destructor, void()); - MOCK_METHOD2(OnMemoryDump, - bool(const MemoryDumpArgs& args, ProcessMemoryDump* pmd)); - MOCK_METHOD1(PollFastMemoryTotal, void(uint64_t* memory_total)); - MOCK_METHOD0(SuspendFastMemoryPolling, void()); - - MockMemoryDumpProvider() : enable_mock_destructor(false) { - ON_CALL(*this, OnMemoryDump(_, _)) - .WillByDefault(Invoke([](const MemoryDumpArgs&, - ProcessMemoryDump* pmd) -> bool { - // |session_state| should not be null under any circumstances when - // invoking a memory dump. The problem might arise in race conditions - // like crbug.com/600570 . - EXPECT_TRUE(pmd->session_state().get() != nullptr); - return true; - })); - - ON_CALL(*this, PollFastMemoryTotal(_)) - .WillByDefault( - Invoke([](uint64_t* memory_total) -> void { NOTREACHED(); })); - } - ~MockMemoryDumpProvider() override { - if (enable_mock_destructor) - Destructor(); - } - - bool enable_mock_destructor; -}; - -class TestSequencedTaskRunner : public SequencedTaskRunner { - public: - TestSequencedTaskRunner() - : worker_pool_(2 /* max_threads */, "Test Task Runner"), - enabled_(true), - num_of_post_tasks_(0) {} - - void set_enabled(bool value) { enabled_ = value; } - unsigned no_of_post_tasks() const { return num_of_post_tasks_; } - - bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here, - OnceClosure task, - TimeDelta delay) override { - NOTREACHED(); - return false; - } - - bool PostDelayedTask(const tracked_objects::Location& from_here, - OnceClosure task, - TimeDelta delay) override { - num_of_post_tasks_++; - if (enabled_) { - return worker_pool_.pool()->PostSequencedWorkerTask(token_, from_here, - std::move(task)); - } - return false; - } - - bool RunsTasksOnCurrentThread() const override { - return worker_pool_.pool()->RunsTasksOnCurrentThread(); - } - - private: - ~TestSequencedTaskRunner() override {} - - SequencedWorkerPoolOwner worker_pool_; - const SequencedWorkerPool::SequenceToken token_; - bool enabled_; - unsigned num_of_post_tasks_; -}; - -} // namespace - -class MemoryDumpManagerTest : public testing::Test { - public: - MemoryDumpManagerTest() : testing::Test(), kDefaultOptions() {} - - void SetUp() override { - last_callback_success_ = false; - message_loop_.reset(new MessageLoop()); - mdm_.reset(new MemoryDumpManager()); - MemoryDumpManager::SetInstanceForTesting(mdm_.get()); - ASSERT_EQ(mdm_.get(), MemoryDumpManager::GetInstance()); - } - - void TearDown() override { - MemoryDumpManager::SetInstanceForTesting(nullptr); - delegate_ = nullptr; - mdm_.reset(); - message_loop_.reset(); - TraceLog::DeleteForTesting(); - } - - // Turns a Closure into a MemoryDumpCallback, keeping track of the callback - // result and taking care of posting the closure on the correct task runner. - void DumpCallbackAdapter(scoped_refptr task_runner, - Closure closure, - uint64_t dump_guid, - bool success) { - last_callback_success_ = success; - task_runner->PostTask(FROM_HERE, closure); - } - - void PollFastMemoryTotal(uint64_t* memory_total) { - mdm_->PollFastMemoryTotal(memory_total); - } - - protected: - void InitializeMemoryDumpManager(bool is_coordinator) { - mdm_->set_dumper_registrations_ignored_for_testing(true); - delegate_ = new MemoryDumpManagerDelegateForTesting(is_coordinator); - mdm_->Initialize(base::WrapUnique(delegate_)); - } - - void RequestGlobalDumpAndWait(MemoryDumpType dump_type, - MemoryDumpLevelOfDetail level_of_detail) { - RunLoop run_loop; - MemoryDumpCallback callback = - Bind(&MemoryDumpManagerTest::DumpCallbackAdapter, Unretained(this), - ThreadTaskRunnerHandle::Get(), run_loop.QuitClosure()); - mdm_->RequestGlobalDump(dump_type, level_of_detail, callback); - run_loop.Run(); - } - - void EnableTracingWithLegacyCategories(const char* category) { - TraceLog::GetInstance()->SetEnabled(TraceConfig(category, ""), - TraceLog::RECORDING_MODE); - } - - void EnableTracingWithTraceConfig(const std::string& trace_config) { - TraceLog::GetInstance()->SetEnabled(TraceConfig(trace_config), - TraceLog::RECORDING_MODE); - } - - void DisableTracing() { TraceLog::GetInstance()->SetDisabled(); } - - bool IsPeriodicDumpingEnabled() const { - return MemoryDumpScheduler::GetInstance() - ->IsPeriodicTimerRunningForTesting(); - } - - int GetMaxConsecutiveFailuresCount() const { - return MemoryDumpManager::kMaxConsecutiveFailuresCount; - } - - const MemoryDumpProvider::Options kDefaultOptions; - std::unique_ptr mdm_; - MemoryDumpManagerDelegateForTesting* delegate_; - bool last_callback_success_; - - private: - std::unique_ptr message_loop_; - - // We want our singleton torn down after each test. - ShadowingAtExitManager at_exit_manager_; -}; - -// Basic sanity checks. Registers a memory dump provider and checks that it is -// called, but only when memory-infra is enabled. -TEST_F(MemoryDumpManagerTest, SingleDumper) { - InitializeMemoryDumpManager(false /* is_coordinator */); - MockMemoryDumpProvider mdp; - RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); - - // Check that the dumper is not called if the memory category is not enabled. - EnableTracingWithLegacyCategories("foobar-but-not-memory"); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(0); - EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - DisableTracing(); - - // Now repeat enabling the memory category and check that the dumper is - // invoked this time. - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(3); - EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(3).WillRepeatedly(Return(true)); - for (int i = 0; i < 3; ++i) - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - DisableTracing(); - - mdm_->UnregisterDumpProvider(&mdp); - - // Finally check the unregister logic: the delegate will be invoked but not - // the dump provider, as it has been unregistered. - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(3); - EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); - - for (int i = 0; i < 3; ++i) { - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - } - DisableTracing(); -} - -// Checks that requesting dumps with high level of detail actually propagates -// the level of the detail properly to OnMemoryDump() call on dump providers. -TEST_F(MemoryDumpManagerTest, CheckMemoryDumpArgs) { - InitializeMemoryDumpManager(false /* is_coordinator */); - MockMemoryDumpProvider mdp; - - RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - EXPECT_CALL(mdp, OnMemoryDump(IsDetailedDump(), _)).WillOnce(Return(true)); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - DisableTracing(); - mdm_->UnregisterDumpProvider(&mdp); - - // Check that requesting dumps with low level of detail actually propagates to - // OnMemoryDump() call on dump providers. - RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - EXPECT_CALL(mdp, OnMemoryDump(IsLightDump(), _)).WillOnce(Return(true)); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::LIGHT); - DisableTracing(); - mdm_->UnregisterDumpProvider(&mdp); -} - -// Checks that the SharedSessionState object is acqually shared over time. -TEST_F(MemoryDumpManagerTest, SharedSessionState) { - InitializeMemoryDumpManager(false /* is_coordinator */); - MockMemoryDumpProvider mdp1; - MockMemoryDumpProvider mdp2; - RegisterDumpProvider(&mdp1, nullptr); - RegisterDumpProvider(&mdp2, nullptr); - - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - const MemoryDumpSessionState* session_state = - mdm_->session_state_for_testing().get(); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(2); - EXPECT_CALL(mdp1, OnMemoryDump(_, _)) - .Times(2) - .WillRepeatedly(Invoke([session_state](const MemoryDumpArgs&, - ProcessMemoryDump* pmd) -> bool { - EXPECT_EQ(session_state, pmd->session_state().get()); - return true; - })); - EXPECT_CALL(mdp2, OnMemoryDump(_, _)) - .Times(2) - .WillRepeatedly(Invoke([session_state](const MemoryDumpArgs&, - ProcessMemoryDump* pmd) -> bool { - EXPECT_EQ(session_state, pmd->session_state().get()); - return true; - })); - - for (int i = 0; i < 2; ++i) { - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - } - - DisableTracing(); -} - -// Checks that the (Un)RegisterDumpProvider logic behaves sanely. -TEST_F(MemoryDumpManagerTest, MultipleDumpers) { - InitializeMemoryDumpManager(false /* is_coordinator */); - MockMemoryDumpProvider mdp1; - MockMemoryDumpProvider mdp2; - - // Enable only mdp1. - RegisterDumpProvider(&mdp1, ThreadTaskRunnerHandle::Get()); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - EXPECT_CALL(mdp1, OnMemoryDump(_, _)).WillOnce(Return(true)); - EXPECT_CALL(mdp2, OnMemoryDump(_, _)).Times(0); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - DisableTracing(); - - // Invert: enable mdp1 and disable mdp2. - mdm_->UnregisterDumpProvider(&mdp1); - RegisterDumpProvider(&mdp2, nullptr); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - EXPECT_CALL(mdp1, OnMemoryDump(_, _)).Times(0); - EXPECT_CALL(mdp2, OnMemoryDump(_, _)).WillOnce(Return(true)); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - DisableTracing(); - - // Enable both mdp1 and mdp2. - RegisterDumpProvider(&mdp1, nullptr); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - EXPECT_CALL(mdp1, OnMemoryDump(_, _)).WillOnce(Return(true)); - EXPECT_CALL(mdp2, OnMemoryDump(_, _)).WillOnce(Return(true)); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - DisableTracing(); -} - -// Checks that the dump provider invocations depend only on the current -// registration state and not on previous registrations and dumps. -// Flaky on iOS, see crbug.com/706874 -#if defined(OS_IOS) -#define MAYBE_RegistrationConsistency DISABLED_RegistrationConsistency -#else -#define MAYBE_RegistrationConsistency RegistrationConsistency -#endif -TEST_F(MemoryDumpManagerTest, MAYBE_RegistrationConsistency) { - InitializeMemoryDumpManager(false /* is_coordinator */); - MockMemoryDumpProvider mdp; - - RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); - - { - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - EXPECT_CALL(mdp, OnMemoryDump(_, _)).WillOnce(Return(true)); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - DisableTracing(); - } - - mdm_->UnregisterDumpProvider(&mdp); - - { - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - DisableTracing(); - } - - RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); - mdm_->UnregisterDumpProvider(&mdp); - - { - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - DisableTracing(); - } - - RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); - mdm_->UnregisterDumpProvider(&mdp); - RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); - - { - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - EXPECT_CALL(mdp, OnMemoryDump(_, _)).WillOnce(Return(true)); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - DisableTracing(); - } -} - -// Checks that the MemoryDumpManager respects the thread affinity when a -// MemoryDumpProvider specifies a task_runner(). The test starts creating 8 -// threads and registering a MemoryDumpProvider on each of them. At each -// iteration, one thread is removed, to check the live unregistration logic. -TEST_F(MemoryDumpManagerTest, RespectTaskRunnerAffinity) { - InitializeMemoryDumpManager(false /* is_coordinator */); - const uint32_t kNumInitialThreads = 8; - - std::vector> threads; - std::vector> mdps; - - // Create the threads and setup the expectations. Given that at each iteration - // we will pop out one thread/MemoryDumpProvider, each MDP is supposed to be - // invoked a number of times equal to its index. - for (uint32_t i = kNumInitialThreads; i > 0; --i) { - threads.push_back(WrapUnique(new Thread("test thread"))); - auto* thread = threads.back().get(); - thread->Start(); - scoped_refptr task_runner = thread->task_runner(); - mdps.push_back(WrapUnique(new MockMemoryDumpProvider())); - auto* mdp = mdps.back().get(); - RegisterDumpProvider(mdp, task_runner, kDefaultOptions); - EXPECT_CALL(*mdp, OnMemoryDump(_, _)) - .Times(i) - .WillRepeatedly(Invoke( - [task_runner](const MemoryDumpArgs&, ProcessMemoryDump*) -> bool { - EXPECT_TRUE(task_runner->RunsTasksOnCurrentThread()); - return true; - })); - } - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - - while (!threads.empty()) { - last_callback_success_ = false; - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - EXPECT_TRUE(last_callback_success_); - - // Unregister a MDP and destroy one thread at each iteration to check the - // live unregistration logic. The unregistration needs to happen on the same - // thread the MDP belongs to. - { - RunLoop run_loop; - Closure unregistration = - Bind(&MemoryDumpManager::UnregisterDumpProvider, - Unretained(mdm_.get()), Unretained(mdps.back().get())); - threads.back()->task_runner()->PostTaskAndReply(FROM_HERE, unregistration, - run_loop.QuitClosure()); - run_loop.Run(); - } - mdps.pop_back(); - threads.back()->Stop(); - threads.pop_back(); - } - - DisableTracing(); -} - -// Check that the memory dump calls are always posted on task runner for -// SequencedTaskRunner case and that the dump provider gets disabled when -// PostTask fails, but the dump still succeeds. -TEST_F(MemoryDumpManagerTest, PostTaskForSequencedTaskRunner) { - InitializeMemoryDumpManager(false /* is_coordinator */); - std::vector mdps(3); - scoped_refptr task_runner1( - make_scoped_refptr(new TestSequencedTaskRunner())); - scoped_refptr task_runner2( - make_scoped_refptr(new TestSequencedTaskRunner())); - RegisterDumpProviderWithSequencedTaskRunner(&mdps[0], task_runner1, - kDefaultOptions); - RegisterDumpProviderWithSequencedTaskRunner(&mdps[1], task_runner2, - kDefaultOptions); - RegisterDumpProviderWithSequencedTaskRunner(&mdps[2], task_runner2, - kDefaultOptions); - // |mdps[0]| should be disabled permanently after first dump. - EXPECT_CALL(mdps[0], OnMemoryDump(_, _)).Times(0); - EXPECT_CALL(mdps[1], OnMemoryDump(_, _)).Times(2); - EXPECT_CALL(mdps[2], OnMemoryDump(_, _)).Times(2); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(2); - - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - - task_runner1->set_enabled(false); - last_callback_success_ = false; - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - // Tasks should be individually posted even if |mdps[1]| and |mdps[2]| belong - // to same task runner. - EXPECT_EQ(1u, task_runner1->no_of_post_tasks()); - EXPECT_EQ(2u, task_runner2->no_of_post_tasks()); - EXPECT_TRUE(last_callback_success_); - - task_runner1->set_enabled(true); - last_callback_success_ = false; - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - EXPECT_EQ(2u, task_runner1->no_of_post_tasks()); - EXPECT_EQ(4u, task_runner2->no_of_post_tasks()); - EXPECT_TRUE(last_callback_success_); - DisableTracing(); -} - -// Checks that providers get disabled after 3 consecutive failures, but not -// otherwise (e.g., if interleaved). -TEST_F(MemoryDumpManagerTest, DisableFailingDumpers) { - InitializeMemoryDumpManager(false /* is_coordinator */); - MockMemoryDumpProvider mdp1; - MockMemoryDumpProvider mdp2; - - RegisterDumpProvider(&mdp1, nullptr); - RegisterDumpProvider(&mdp2, nullptr); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - - const int kNumDumps = 2 * GetMaxConsecutiveFailuresCount(); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(kNumDumps); - - EXPECT_CALL(mdp1, OnMemoryDump(_, _)) - .Times(GetMaxConsecutiveFailuresCount()) - .WillRepeatedly(Return(false)); - - EXPECT_CALL(mdp2, OnMemoryDump(_, _)) - .WillOnce(Return(false)) - .WillOnce(Return(true)) - .WillOnce(Return(false)) - .WillOnce(Return(false)) - .WillOnce(Return(true)) - .WillOnce(Return(false)); - - for (int i = 0; i < kNumDumps; i++) { - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - } - - DisableTracing(); -} - -// Sneakily registers an extra memory dump provider while an existing one is -// dumping and expect it to take part in the already active tracing session. -TEST_F(MemoryDumpManagerTest, RegisterDumperWhileDumping) { - InitializeMemoryDumpManager(false /* is_coordinator */); - MockMemoryDumpProvider mdp1; - MockMemoryDumpProvider mdp2; - - RegisterDumpProvider(&mdp1, nullptr); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(4); - - EXPECT_CALL(mdp1, OnMemoryDump(_, _)) - .Times(4) - .WillOnce(Return(true)) - .WillOnce( - Invoke([&mdp2](const MemoryDumpArgs&, ProcessMemoryDump*) -> bool { - RegisterDumpProvider(&mdp2, nullptr); - return true; - })) - .WillRepeatedly(Return(true)); - - // Depending on the insertion order (before or after mdp1), mdp2 might be - // called also immediately after it gets registered. - EXPECT_CALL(mdp2, OnMemoryDump(_, _)) - .Times(Between(2, 3)) - .WillRepeatedly(Return(true)); - - for (int i = 0; i < 4; i++) { - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - } - - DisableTracing(); -} - -// Like RegisterDumperWhileDumping, but unregister the dump provider instead. -TEST_F(MemoryDumpManagerTest, UnregisterDumperWhileDumping) { - InitializeMemoryDumpManager(false /* is_coordinator */); - MockMemoryDumpProvider mdp1; - MockMemoryDumpProvider mdp2; - - RegisterDumpProvider(&mdp1, ThreadTaskRunnerHandle::Get(), kDefaultOptions); - RegisterDumpProvider(&mdp2, ThreadTaskRunnerHandle::Get(), kDefaultOptions); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(4); - - EXPECT_CALL(mdp1, OnMemoryDump(_, _)) - .Times(4) - .WillOnce(Return(true)) - .WillOnce( - Invoke([&mdp2](const MemoryDumpArgs&, ProcessMemoryDump*) -> bool { - MemoryDumpManager::GetInstance()->UnregisterDumpProvider(&mdp2); - return true; - })) - .WillRepeatedly(Return(true)); - - // Depending on the insertion order (before or after mdp1), mdp2 might have - // been already called when UnregisterDumpProvider happens. - EXPECT_CALL(mdp2, OnMemoryDump(_, _)) - .Times(Between(1, 2)) - .WillRepeatedly(Return(true)); - - for (int i = 0; i < 4; i++) { - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - } - - DisableTracing(); -} - -// Checks that the dump does not abort when unregistering a provider while -// dumping from a different thread than the dumping thread. -TEST_F(MemoryDumpManagerTest, UnregisterDumperFromThreadWhileDumping) { - InitializeMemoryDumpManager(false /* is_coordinator */); - std::vector> threads; - std::vector> mdps; - - for (int i = 0; i < 2; i++) { - threads.push_back( - WrapUnique(new TestIOThread(TestIOThread::kAutoStart))); - mdps.push_back(WrapUnique(new MockMemoryDumpProvider())); - RegisterDumpProvider(mdps.back().get(), threads.back()->task_runner(), - kDefaultOptions); - } - - int on_memory_dump_call_count = 0; - - // When OnMemoryDump is called on either of the dump providers, it will - // unregister the other one. - for (const std::unique_ptr& mdp : mdps) { - int other_idx = (mdps.front() == mdp); - // TestIOThread's task runner must be obtained from the main thread but can - // then be used from other threads. - scoped_refptr other_runner = - threads[other_idx]->task_runner(); - MockMemoryDumpProvider* other_mdp = mdps[other_idx].get(); - auto on_dump = [this, other_runner, other_mdp, &on_memory_dump_call_count]( - const MemoryDumpArgs& args, ProcessMemoryDump* pmd) { - PostTaskAndWait(FROM_HERE, other_runner.get(), - base::Bind(&MemoryDumpManager::UnregisterDumpProvider, - base::Unretained(&*mdm_), other_mdp)); - on_memory_dump_call_count++; - return true; - }; - - // OnMemoryDump is called once for the provider that dumps first, and zero - // times for the other provider. - EXPECT_CALL(*mdp, OnMemoryDump(_, _)) - .Times(AtMost(1)) - .WillOnce(Invoke(on_dump)); - } - - last_callback_success_ = false; - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - ASSERT_EQ(1, on_memory_dump_call_count); - ASSERT_TRUE(last_callback_success_); - - DisableTracing(); -} - -TEST_F(MemoryDumpManagerTest, TestPollingOnDumpThread) { - InitializeMemoryDumpManager(false /* is_coordinator */); - std::unique_ptr mdp1(new MockMemoryDumpProvider()); - std::unique_ptr mdp2(new MockMemoryDumpProvider()); - mdp1->enable_mock_destructor = true; - mdp2->enable_mock_destructor = true; - - EXPECT_CALL(*mdp1, SuspendFastMemoryPolling()).Times(1); - EXPECT_CALL(*mdp2, SuspendFastMemoryPolling()).Times(1); - EXPECT_CALL(*mdp1, Destructor()); - EXPECT_CALL(*mdp2, Destructor()); - - MemoryDumpProvider::Options options; - options.is_fast_polling_supported = true; - RegisterDumpProvider(mdp1.get(), nullptr, options); - - RunLoop run_loop; - scoped_refptr test_task_runner = - ThreadTaskRunnerHandle::Get(); - auto quit_closure = run_loop.QuitClosure(); - - const int kPollsToQuit = 10; - int call_count = 0; - MemoryDumpManager* mdm = mdm_.get(); - const auto poll_function1 = [&call_count, &test_task_runner, quit_closure, - &mdp2, mdm, &options, kPollsToQuit, - this](uint64_t* total) -> void { - ++call_count; - if (call_count == 1) - RegisterDumpProvider(mdp2.get(), nullptr, options, kMDPName); - else if (call_count == 4) - mdm->UnregisterAndDeleteDumpProviderSoon(std::move(mdp2)); - else if (call_count == kPollsToQuit) - test_task_runner->PostTask(FROM_HERE, quit_closure); - - // Record increase of 1 GiB of memory at each call. - *total = static_cast(call_count) * 1024 * 1024 * 1024; - }; - EXPECT_CALL(*mdp1, PollFastMemoryTotal(_)) - .Times(testing::AtLeast(kPollsToQuit)) - .WillRepeatedly(Invoke(poll_function1)); - - // Depending on the order of PostTask calls the mdp2 might be registered after - // all polls or in between polls. - EXPECT_CALL(*mdp2, PollFastMemoryTotal(_)) - .Times(Between(0, kPollsToQuit - 1)) - .WillRepeatedly(Return()); - - MemoryDumpScheduler::SetPollingIntervalForTesting(1); - EnableTracingWithTraceConfig( - TraceConfigMemoryTestUtil::GetTraceConfig_PeakDetectionTrigger(3)); - - int last_poll_to_request_dump = -2; - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)) - .Times(testing::AtLeast(2)) - .WillRepeatedly(Invoke([&last_poll_to_request_dump, &call_count]( - const MemoryDumpRequestArgs& args, - const MemoryDumpCallback& callback) -> void { - // Minimum number of polls between dumps must be 3 (polling interval is - // 1ms). - EXPECT_GE(call_count - last_poll_to_request_dump, 3); - last_poll_to_request_dump = call_count; - })); - - run_loop.Run(); - DisableTracing(); - mdm_->UnregisterAndDeleteDumpProviderSoon(std::move(mdp1)); -} - -// If a thread (with a dump provider living on it) is torn down during a dump -// its dump provider should be skipped but the dump itself should succeed. -TEST_F(MemoryDumpManagerTest, TearDownThreadWhileDumping) { - InitializeMemoryDumpManager(false /* is_coordinator */); - std::vector> threads; - std::vector> mdps; - - for (int i = 0; i < 2; i++) { - threads.push_back( - WrapUnique(new TestIOThread(TestIOThread::kAutoStart))); - mdps.push_back(WrapUnique(new MockMemoryDumpProvider())); - RegisterDumpProvider(mdps.back().get(), threads.back()->task_runner(), - kDefaultOptions); - } - - int on_memory_dump_call_count = 0; - - // When OnMemoryDump is called on either of the dump providers, it will - // tear down the thread of the other one. - for (const std::unique_ptr& mdp : mdps) { - int other_idx = (mdps.front() == mdp); - TestIOThread* other_thread = threads[other_idx].get(); - // TestIOThread isn't thread-safe and must be stopped on the |main_runner|. - scoped_refptr main_runner = - SequencedTaskRunnerHandle::Get(); - auto on_dump = [other_thread, main_runner, &on_memory_dump_call_count]( - const MemoryDumpArgs& args, ProcessMemoryDump* pmd) { - PostTaskAndWait( - FROM_HERE, main_runner.get(), - base::Bind(&TestIOThread::Stop, base::Unretained(other_thread))); - on_memory_dump_call_count++; - return true; - }; - - // OnMemoryDump is called once for the provider that dumps first, and zero - // times for the other provider. - EXPECT_CALL(*mdp, OnMemoryDump(_, _)) - .Times(AtMost(1)) - .WillOnce(Invoke(on_dump)); - } - - last_callback_success_ = false; - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - ASSERT_EQ(1, on_memory_dump_call_count); - ASSERT_TRUE(last_callback_success_); - - DisableTracing(); -} - -// Checks that a NACK callback is invoked if RequestGlobalDump() is called when -// tracing is not enabled. -TEST_F(MemoryDumpManagerTest, CallbackCalledOnFailure) { - InitializeMemoryDumpManager(false /* is_coordinator */); - MockMemoryDumpProvider mdp1; - RegisterDumpProvider(&mdp1, nullptr); - - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(0); - EXPECT_CALL(mdp1, OnMemoryDump(_, _)).Times(0); - - last_callback_success_ = true; - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - EXPECT_FALSE(last_callback_success_); -} - -// Checks that is the MemoryDumpManager is initialized after tracing already -// began, it will still late-join the party (real use case: startup tracing). -TEST_F(MemoryDumpManagerTest, InitializedAfterStartOfTracing) { - MockMemoryDumpProvider mdp; - RegisterDumpProvider(&mdp, nullptr); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - - // First check that a RequestGlobalDump() issued before the MemoryDumpManager - // initialization gets NACK-ed cleanly. - { - EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - EXPECT_FALSE(last_callback_success_); - } - - // Now late-initialize the MemoryDumpManager and check that the - // RequestGlobalDump completes successfully. - { - InitializeMemoryDumpManager(false /* is_coordinator */); - EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(1); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - EXPECT_TRUE(last_callback_success_); - } - DisableTracing(); -} - -// This test (and the MemoryDumpManagerTestCoordinator below) crystallizes the -// expectations of the chrome://tracing UI and chrome telemetry w.r.t. periodic -// dumps in memory-infra, handling gracefully the transition between the legacy -// and the new-style (JSON-based) TraceConfig. -TEST_F(MemoryDumpManagerTest, TraceConfigExpectations) { - InitializeMemoryDumpManager(false /* is_coordinator */); - MemoryDumpManagerDelegateForTesting& delegate = *delegate_; - - // Don't trigger the default behavior of the mock delegate in this test, - // which would short-circuit the dump request to the actual - // CreateProcessDump(). - // We don't want to create any dump in this test, only check whether the dumps - // are requested or not. - ON_CALL(delegate, RequestGlobalMemoryDump(_, _)).WillByDefault(Return()); - - // Enabling memory-infra in a non-coordinator process should not trigger any - // periodic dumps. - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_FALSE(IsPeriodicDumpingEnabled()); - DisableTracing(); - - // Enabling memory-infra with the new (JSON) TraceConfig in a non-coordinator - // process with a fully defined trigger config should NOT enable any periodic - // dumps. - EnableTracingWithTraceConfig( - TraceConfigMemoryTestUtil::GetTraceConfig_PeriodicTriggers(1, 5)); - EXPECT_FALSE(IsPeriodicDumpingEnabled()); - DisableTracing(); -} - -TEST_F(MemoryDumpManagerTest, TraceConfigExpectationsWhenIsCoordinator) { - InitializeMemoryDumpManager(true /* is_coordinator */); - MemoryDumpManagerDelegateForTesting& delegate = *delegate_; - ON_CALL(delegate, RequestGlobalMemoryDump(_, _)).WillByDefault(Return()); - - // Enabling memory-infra with the legacy TraceConfig (category filter) in - // a coordinator process should enable periodic dumps. - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_TRUE(IsPeriodicDumpingEnabled()); - DisableTracing(); - - // Enabling memory-infra with the new (JSON) TraceConfig in a coordinator - // process without specifying any "memory_dump_config" section should enable - // periodic dumps. This is to preserve the behavior chrome://tracing UI, that - // is: ticking memory-infra should dump periodically with the default config. - EnableTracingWithTraceConfig( - TraceConfigMemoryTestUtil::GetTraceConfig_NoTriggers()); - EXPECT_TRUE(IsPeriodicDumpingEnabled()); - DisableTracing(); - - // Enabling memory-infra with the new (JSON) TraceConfig in a coordinator - // process with an empty "memory_dump_config" should NOT enable periodic - // dumps. This is the way telemetry is supposed to use memory-infra with - // only explicitly triggered dumps. - EnableTracingWithTraceConfig( - TraceConfigMemoryTestUtil::GetTraceConfig_EmptyTriggers()); - EXPECT_FALSE(IsPeriodicDumpingEnabled()); - DisableTracing(); - - // Enabling memory-infra with the new (JSON) TraceConfig in a coordinator - // process with a fully defined trigger config should cause periodic dumps to - // be performed in the correct order. - RunLoop run_loop; - auto quit_closure = run_loop.QuitClosure(); - - const int kHeavyDumpRate = 5; - const int kLightDumpPeriodMs = 1; - const int kHeavyDumpPeriodMs = kHeavyDumpRate * kLightDumpPeriodMs; - // The expected sequence with light=1ms, heavy=5ms is H,L,L,L,L,H,... - testing::InSequence sequence; - EXPECT_CALL(delegate, RequestGlobalMemoryDump(IsDetailedDump(), _)); - EXPECT_CALL(delegate, RequestGlobalMemoryDump(IsLightDump(), _)) - .Times(kHeavyDumpRate - 1); - EXPECT_CALL(delegate, RequestGlobalMemoryDump(IsDetailedDump(), _)); - EXPECT_CALL(delegate, RequestGlobalMemoryDump(IsLightDump(), _)) - .Times(kHeavyDumpRate - 2); - EXPECT_CALL(delegate, RequestGlobalMemoryDump(IsLightDump(), _)) - .WillOnce(Invoke([quit_closure](const MemoryDumpRequestArgs& args, - const MemoryDumpCallback& callback) { - ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, quit_closure); - })); - - // Swallow all the final spurious calls until tracing gets disabled. - EXPECT_CALL(delegate, RequestGlobalMemoryDump(_, _)).Times(AnyNumber()); - - EnableTracingWithTraceConfig( - TraceConfigMemoryTestUtil::GetTraceConfig_PeriodicTriggers( - kLightDumpPeriodMs, kHeavyDumpPeriodMs)); - run_loop.Run(); - DisableTracing(); -} - -// Tests against race conditions that might arise when disabling tracing in the -// middle of a global memory dump. -// Flaky on iOS, see crbug.com/706961 -#if defined(OS_IOS) -#define MAYBE_DisableTracingWhileDumping DISABLED_DisableTracingWhileDumping -#else -#define MAYBE_DisableTracingWhileDumping DisableTracingWhileDumping -#endif -TEST_F(MemoryDumpManagerTest, MAYBE_DisableTracingWhileDumping) { - base::WaitableEvent tracing_disabled_event( - WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - InitializeMemoryDumpManager(false /* is_coordinator */); - - // Register a bound dump provider. - std::unique_ptr mdp_thread(new Thread("test thread")); - mdp_thread->Start(); - MockMemoryDumpProvider mdp_with_affinity; - RegisterDumpProvider(&mdp_with_affinity, mdp_thread->task_runner(), - kDefaultOptions); - - // Register also an unbound dump provider. Unbound dump providers are always - // invoked after bound ones. - MockMemoryDumpProvider unbound_mdp; - RegisterDumpProvider(&unbound_mdp, nullptr, kDefaultOptions); - - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - EXPECT_CALL(mdp_with_affinity, OnMemoryDump(_, _)) - .Times(1) - .WillOnce( - Invoke([&tracing_disabled_event](const MemoryDumpArgs&, - ProcessMemoryDump* pmd) -> bool { - tracing_disabled_event.Wait(); - - // At this point tracing has been disabled and the - // MemoryDumpManager.dump_thread_ has been shut down. - return true; - })); - - // |unbound_mdp| should never be invoked because the thread for unbound dump - // providers has been shutdown in the meanwhile. - EXPECT_CALL(unbound_mdp, OnMemoryDump(_, _)).Times(0); - - last_callback_success_ = true; - RunLoop run_loop; - MemoryDumpCallback callback = - Bind(&MemoryDumpManagerTest::DumpCallbackAdapter, Unretained(this), - ThreadTaskRunnerHandle::Get(), run_loop.QuitClosure()); - mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED, callback); - DisableTracing(); - tracing_disabled_event.Signal(); - run_loop.Run(); - - EXPECT_FALSE(last_callback_success_); -} - -// Tests against race conditions that can happen if tracing is disabled before -// the CreateProcessDump() call. Real-world regression: crbug.com/580295 . -TEST_F(MemoryDumpManagerTest, DisableTracingRightBeforeStartOfDump) { - base::WaitableEvent tracing_disabled_event( - WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - InitializeMemoryDumpManager(false /* is_coordinator */); - - std::unique_ptr mdp_thread(new Thread("test thread")); - mdp_thread->Start(); - - // Create both same-thread MDP and another MDP with dedicated thread - MockMemoryDumpProvider mdp1; - RegisterDumpProvider(&mdp1, nullptr); - MockMemoryDumpProvider mdp2; - RegisterDumpProvider(&mdp2, mdp_thread->task_runner(), kDefaultOptions); - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)) - .WillOnce(Invoke([this](const MemoryDumpRequestArgs& args, - const MemoryDumpCallback& callback) { - DisableTracing(); - delegate_->CreateProcessDump(args, callback); - })); - - // If tracing is disabled for current session CreateProcessDump() should NOT - // request dumps from providers. Real-world regression: crbug.com/600570 . - EXPECT_CALL(mdp1, OnMemoryDump(_, _)).Times(0); - EXPECT_CALL(mdp2, OnMemoryDump(_, _)).Times(0); - - last_callback_success_ = true; - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - EXPECT_FALSE(last_callback_success_); -} - -TEST_F(MemoryDumpManagerTest, DumpOnBehalfOfOtherProcess) { - using trace_analyzer::Query; - - InitializeMemoryDumpManager(false /* is_coordinator */); - - // Standard provider with default options (create dump for current process). - MemoryDumpProvider::Options options; - MockMemoryDumpProvider mdp1; - RegisterDumpProvider(&mdp1, nullptr, options); - - // Provider with out-of-process dumping. - MockMemoryDumpProvider mdp2; - options.target_pid = 123; - RegisterDumpProvider(&mdp2, nullptr, options); - - // Another provider with out-of-process dumping. - MockMemoryDumpProvider mdp3; - options.target_pid = 456; - RegisterDumpProvider(&mdp3, nullptr, options); - - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - EXPECT_CALL(mdp1, OnMemoryDump(_, _)).Times(1).WillRepeatedly(Return(true)); - EXPECT_CALL(mdp2, OnMemoryDump(_, _)).Times(1).WillRepeatedly(Return(true)); - EXPECT_CALL(mdp3, OnMemoryDump(_, _)).Times(1).WillRepeatedly(Return(true)); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - DisableTracing(); - - // Flush the trace into JSON. - trace_event::TraceResultBuffer buffer; - TraceResultBuffer::SimpleOutput trace_output; - buffer.SetOutputCallback(trace_output.GetCallback()); - RunLoop run_loop; - buffer.Start(); - trace_event::TraceLog::GetInstance()->Flush( - Bind(&OnTraceDataCollected, run_loop.QuitClosure(), Unretained(&buffer))); - run_loop.Run(); - buffer.Finish(); - - // Analyze the JSON. - std::unique_ptr analyzer = WrapUnique( - trace_analyzer::TraceAnalyzer::Create(trace_output.json_output)); - trace_analyzer::TraceEventVector events; - analyzer->FindEvents(Query::EventPhaseIs(TRACE_EVENT_PHASE_MEMORY_DUMP), - &events); - - ASSERT_EQ(3u, events.size()); - ASSERT_EQ(1u, trace_analyzer::CountMatches(events, Query::EventPidIs(123))); - ASSERT_EQ(1u, trace_analyzer::CountMatches(events, Query::EventPidIs(456))); - ASSERT_EQ(1u, trace_analyzer::CountMatches( - events, Query::EventPidIs(GetCurrentProcId()))); - ASSERT_EQ(events[0]->id, events[1]->id); - ASSERT_EQ(events[0]->id, events[2]->id); -} - -// Tests the basics of the UnregisterAndDeleteDumpProviderSoon(): the -// unregistration should actually delete the providers and not leak them. -TEST_F(MemoryDumpManagerTest, UnregisterAndDeleteDumpProviderSoon) { - InitializeMemoryDumpManager(false /* is_coordinator */); - static const int kNumProviders = 3; - int dtor_count = 0; - std::vector> mdps; - for (int i = 0; i < kNumProviders; ++i) { - std::unique_ptr mdp(new MockMemoryDumpProvider); - mdp->enable_mock_destructor = true; - EXPECT_CALL(*mdp, Destructor()) - .WillOnce(Invoke([&dtor_count]() { dtor_count++; })); - RegisterDumpProvider(mdp.get(), nullptr, kDefaultOptions); - mdps.push_back(std::move(mdp)); - } - - while (!mdps.empty()) { - mdm_->UnregisterAndDeleteDumpProviderSoon(std::move(mdps.back())); - mdps.pop_back(); - } - - ASSERT_EQ(kNumProviders, dtor_count); -} - -// This test checks against races when unregistering an unbound dump provider -// from another thread while dumping. It registers one MDP and, when -// OnMemoryDump() is called, it invokes UnregisterAndDeleteDumpProviderSoon() -// from another thread. The OnMemoryDump() and the dtor call are expected to -// happen on the same thread (the MemoryDumpManager utility thread). -TEST_F(MemoryDumpManagerTest, UnregisterAndDeleteDumpProviderSoonDuringDump) { - InitializeMemoryDumpManager(false /* is_coordinator */); - std::unique_ptr mdp(new MockMemoryDumpProvider); - mdp->enable_mock_destructor = true; - RegisterDumpProvider(mdp.get(), nullptr, kDefaultOptions); - - base::PlatformThreadRef thread_ref; - auto self_unregister_from_another_thread = [&mdp, &thread_ref]( - const MemoryDumpArgs&, ProcessMemoryDump*) -> bool { - thread_ref = PlatformThread::CurrentRef(); - TestIOThread thread_for_unregistration(TestIOThread::kAutoStart); - PostTaskAndWait( - FROM_HERE, thread_for_unregistration.task_runner().get(), - base::Bind( - &MemoryDumpManager::UnregisterAndDeleteDumpProviderSoon, - base::Unretained(MemoryDumpManager::GetInstance()), - base::Passed(std::unique_ptr(std::move(mdp))))); - thread_for_unregistration.Stop(); - return true; - }; - EXPECT_CALL(*mdp, OnMemoryDump(_, _)) - .Times(1) - .WillOnce(Invoke(self_unregister_from_another_thread)); - EXPECT_CALL(*mdp, Destructor()) - .Times(1) - .WillOnce(Invoke([&thread_ref]() { - EXPECT_EQ(thread_ref, PlatformThread::CurrentRef()); - })); - - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(2); - for (int i = 0; i < 2; ++i) { - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - } - DisableTracing(); -} - -TEST_F(MemoryDumpManagerTest, TestWhitelistingMDP) { - InitializeMemoryDumpManager(false /* is_coordinator */); - SetDumpProviderWhitelistForTesting(kTestMDPWhitelist); - std::unique_ptr mdp1(new MockMemoryDumpProvider); - RegisterDumpProvider(mdp1.get(), nullptr); - std::unique_ptr mdp2(new MockMemoryDumpProvider); - RegisterDumpProvider(mdp2.get(), nullptr, kDefaultOptions, - kWhitelistedMDPName); - - EXPECT_CALL(*mdp1, OnMemoryDump(_, _)).Times(0); - EXPECT_CALL(*mdp2, OnMemoryDump(_, _)).Times(1).WillOnce(Return(true)); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); - - EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); - EXPECT_FALSE(IsPeriodicDumpingEnabled()); - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::BACKGROUND); - DisableTracing(); -} - -TEST_F(MemoryDumpManagerTest, TestBackgroundTracingSetup) { - InitializeMemoryDumpManager(true /* is_coordinator */); - - RunLoop run_loop; - auto quit_closure = run_loop.QuitClosure(); - - testing::InSequence sequence; - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(IsBackgroundDump(), _)) - .Times(5); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(IsBackgroundDump(), _)) - .WillOnce(Invoke([quit_closure](const MemoryDumpRequestArgs& args, - const MemoryDumpCallback& callback) { - ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, quit_closure); - })); - EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(AnyNumber()); - - EnableTracingWithTraceConfig( - TraceConfigMemoryTestUtil::GetTraceConfig_BackgroundTrigger( - 1 /* period_ms */)); - - // Only background mode dumps should be allowed with the trace config. - last_callback_success_ = false; - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::LIGHT); - EXPECT_FALSE(last_callback_success_); - last_callback_success_ = false; - RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, - MemoryDumpLevelOfDetail::DETAILED); - EXPECT_FALSE(last_callback_success_); - - ASSERT_TRUE(IsPeriodicDumpingEnabled()); - run_loop.Run(); - DisableTracing(); -} - -TEST_F(MemoryDumpManagerTest, TestBlacklistedUnsafeUnregistration) { - InitializeMemoryDumpManager(false /* is_coordinator */); - MockMemoryDumpProvider mdp1; - RegisterDumpProvider(&mdp1, nullptr, kDefaultOptions, - "BlacklistTestDumpProvider"); - // Not calling UnregisterAndDeleteDumpProviderSoon() should not crash. - mdm_->UnregisterDumpProvider(&mdp1); - - Thread thread("test thread"); - thread.Start(); - RegisterDumpProvider(&mdp1, thread.task_runner(), kDefaultOptions, - "BlacklistTestDumpProvider"); - // Unregistering on wrong thread should not crash. - mdm_->UnregisterDumpProvider(&mdp1); - thread.Stop(); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_dump_provider.h b/base/trace_event/memory_dump_provider.h deleted file mode 100644 index 244319efa7b7996c4b45e399028886cc3e61ee06..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_provider.h +++ /dev/null @@ -1,82 +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. - -#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_H_ -#define BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_H_ - -#include "base/base_export.h" -#include "base/macros.h" -#include "base/process/process_handle.h" -#include "base/trace_event/memory_dump_request_args.h" - -namespace base { -namespace trace_event { - -class ProcessMemoryDump; - -// The contract interface that memory dump providers must implement. -class BASE_EXPORT MemoryDumpProvider { - public: - // Optional arguments for MemoryDumpManager::RegisterDumpProvider(). - struct Options { - Options() - : target_pid(kNullProcessId), - dumps_on_single_thread_task_runner(false), - is_fast_polling_supported(false) {} - - // If the dump provider generates dumps on behalf of another process, - // |target_pid| contains the pid of that process. - // The default value is kNullProcessId, which means that the dump provider - // generates dumps for the current process. - ProcessId target_pid; - - // |dumps_on_single_thread_task_runner| is true if the dump provider runs on - // a SingleThreadTaskRunner, which is usually the case. It is faster to run - // all providers that run on the same thread together without thread hops. - bool dumps_on_single_thread_task_runner; - - // Set to true if the dump provider implementation supports high frequency - // polling. Only providers running without task runner affinity are - // supported. - bool is_fast_polling_supported; - }; - - virtual ~MemoryDumpProvider() {} - - // Called by the MemoryDumpManager when generating memory dumps. - // The |args| specify if the embedder should generate light/heavy dumps on - // dump requests. The embedder should return true if the |pmd| was - // successfully populated, false if something went wrong and the dump should - // be considered invalid. - // (Note, the MemoryDumpManager has a fail-safe logic which will disable the - // MemoryDumpProvider for the entire trace session if it fails consistently). - virtual bool OnMemoryDump(const MemoryDumpArgs& args, - ProcessMemoryDump* pmd) = 0; - - // Called by the MemoryDumpManager when an allocator should start or stop - // collecting extensive allocation data, if supported. - virtual void OnHeapProfilingEnabled(bool enabled) {} - - // Quickly record the total memory usage in |memory_total|. This method will - // be called only when the dump provider registration has - // |is_fast_polling_supported| set to true. This method is used for polling at - // high frequency for detecting peaks. See comment on - // |is_fast_polling_supported| option if you need to override this method. - virtual void PollFastMemoryTotal(uint64_t* memory_total) {} - - // Indicates that fast memory polling is not going to be used in the near - // future and the MDP can tear down any resource kept around for fast memory - // polling. - virtual void SuspendFastMemoryPolling() {} - - protected: - MemoryDumpProvider() {} - - DISALLOW_COPY_AND_ASSIGN(MemoryDumpProvider); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_H_ diff --git a/base/trace_event/memory_dump_provider_info.cc b/base/trace_event/memory_dump_provider_info.cc deleted file mode 100644 index 6bb711018ba5559e2d678dec54b23787cb0bf74b..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_provider_info.cc +++ /dev/null @@ -1,43 +0,0 @@ -// 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. - -#include "base/trace_event/memory_dump_provider_info.h" - -#include - -#include "base/sequenced_task_runner.h" - -namespace base { -namespace trace_event { - -MemoryDumpProviderInfo::MemoryDumpProviderInfo( - MemoryDumpProvider* dump_provider, - const char* name, - scoped_refptr task_runner, - const MemoryDumpProvider::Options& options, - bool whitelisted_for_background_mode) - : dump_provider(dump_provider), - options(options), - name(name), - task_runner(std::move(task_runner)), - whitelisted_for_background_mode(whitelisted_for_background_mode), - consecutive_failures(0), - disabled(false) {} - -MemoryDumpProviderInfo::~MemoryDumpProviderInfo() {} - -bool MemoryDumpProviderInfo::Comparator::operator()( - const scoped_refptr& a, - const scoped_refptr& b) const { - if (!a || !b) - return a.get() < b.get(); - // Ensure that unbound providers (task_runner == nullptr) always run last. - // Rationale: some unbound dump providers are known to be slow, keep them last - // to avoid skewing timings of the other dump providers. - return std::tie(a->task_runner, a->dump_provider) > - std::tie(b->task_runner, b->dump_provider); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_dump_provider_info.h b/base/trace_event/memory_dump_provider_info.h deleted file mode 100644 index ca63a987b2c4398a3c734001b19c7de1c886f8b0..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_provider_info.h +++ /dev/null @@ -1,108 +0,0 @@ -// 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. - -#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_INFO_H_ -#define BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_INFO_H_ - -#include -#include - -#include "base/base_export.h" -#include "base/memory/ref_counted.h" -#include "base/trace_event/memory_dump_provider.h" - -namespace base { - -class SequencedTaskRunner; - -namespace trace_event { - -// Wraps a MemoryDumpProvider (MDP), which is registered via -// MemoryDumpManager(MDM)::RegisterDumpProvider(), holding the extra information -// required to deal with it (which task runner it should be invoked onto, -// whether it has been disabled, etc.) -// More importantly, having a refptr to this object guarantees that a MDP that -// is not thread-bound (hence which can only be unregistered via -// MDM::UnregisterAndDeleteDumpProviderSoon()) will stay alive as long as the -// refptr is held. -// -// Lifetime: -// At any time, there is at most one instance of this class for each instance -// of a given MemoryDumpProvider, but there might be several scoped_refptr -// holding onto each of this. Specifically: -// - In nominal conditions, there is a refptr for each registerd MDP in the -// MDM's |dump_providers_| list. -// - In most cases, the only refptr (in the |dump_providers_| list) is destroyed -// by MDM::UnregisterDumpProvider(). -// - However, when MDM starts a dump, the list of refptrs is copied into the -// ProcessMemoryDumpAsyncState. That list is pruned as MDP(s) are invoked. -// - If UnregisterDumpProvider() is called on a non-thread-bound MDP while a -// dump is in progress, the extar extra of the handle is destroyed in -// MDM::SetupNextMemoryDump() or MDM::InvokeOnMemoryDump(), when the copy -// inside ProcessMemoryDumpAsyncState is erase()-d. -// - The PeakDetector can keep extra refptrs when enabled. -struct BASE_EXPORT MemoryDumpProviderInfo - : public RefCountedThreadSafe { - public: - // Define a total order based on the |task_runner| affinity, so that MDPs - // belonging to the same SequencedTaskRunner are adjacent in the set. - struct Comparator { - bool operator()(const scoped_refptr& a, - const scoped_refptr& b) const; - }; - using OrderedSet = - std::set, Comparator>; - - MemoryDumpProviderInfo(MemoryDumpProvider* dump_provider, - const char* name, - scoped_refptr task_runner, - const MemoryDumpProvider::Options& options, - bool whitelisted_for_background_mode); - - // It is safe to access the const fields below from any thread as they are - // never mutated. - - MemoryDumpProvider* const dump_provider; - - // The |options| arg passed to MDM::RegisterDumpProvider(). - const MemoryDumpProvider::Options options; - - // Human readable name, not unique (distinct MDP instances might have the same - // name). Used for debugging, testing and whitelisting for BACKGROUND mode. - const char* const name; - - // The task runner on which the MDP::OnMemoryDump call should be posted onto. - // Can be nullptr, in which case the MDP will be invoked on a background - // thread handled by MDM. - const scoped_refptr task_runner; - - // True if the dump provider is whitelisted for background mode. - const bool whitelisted_for_background_mode; - - // These fields below, instead, are not thread safe and can be mutated only: - // - On the |task_runner|, when not null (i.e. for thread-bound MDPS). - // - By the MDM's background thread (or in any other way that guarantees - // sequencing) for non-thread-bound MDPs. - - // Used to transfer ownership for UnregisterAndDeleteDumpProviderSoon(). - // nullptr in all other cases. - std::unique_ptr owned_dump_provider; - - // For fail-safe logic (auto-disable failing MDPs). - int consecutive_failures; - - // Flagged either by the auto-disable logic or during unregistration. - bool disabled; - - private: - friend class base::RefCountedThreadSafe; - ~MemoryDumpProviderInfo(); - - DISALLOW_COPY_AND_ASSIGN(MemoryDumpProviderInfo); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_INFO_H_ diff --git a/base/trace_event/memory_dump_request_args.cc b/base/trace_event/memory_dump_request_args.cc deleted file mode 100644 index f2744007d7e27086101906832b76f2a442a2c67e..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_request_args.cc +++ /dev/null @@ -1,68 +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. - -#include "base/trace_event/memory_dump_request_args.h" - -#include "base/logging.h" - -namespace base { -namespace trace_event { - -// static -const char* MemoryDumpTypeToString(const MemoryDumpType& dump_type) { - switch (dump_type) { - case MemoryDumpType::PERIODIC_INTERVAL: - return "periodic_interval"; - case MemoryDumpType::EXPLICITLY_TRIGGERED: - return "explicitly_triggered"; - case MemoryDumpType::PEAK_MEMORY_USAGE: - return "peak_memory_usage"; - } - NOTREACHED(); - return "unknown"; -} - -MemoryDumpType StringToMemoryDumpType(const std::string& str) { - if (str == "periodic_interval") - return MemoryDumpType::PERIODIC_INTERVAL; - if (str == "explicitly_triggered") - return MemoryDumpType::EXPLICITLY_TRIGGERED; - if (str == "peak_memory_usage") - return MemoryDumpType::PEAK_MEMORY_USAGE; - NOTREACHED(); - return MemoryDumpType::LAST; -} - -const char* MemoryDumpLevelOfDetailToString( - const MemoryDumpLevelOfDetail& level_of_detail) { - switch (level_of_detail) { - case MemoryDumpLevelOfDetail::BACKGROUND: - return "background"; - case MemoryDumpLevelOfDetail::LIGHT: - return "light"; - case MemoryDumpLevelOfDetail::DETAILED: - return "detailed"; - } - NOTREACHED(); - return "unknown"; -} - -MemoryDumpLevelOfDetail StringToMemoryDumpLevelOfDetail( - const std::string& str) { - if (str == "background") - return MemoryDumpLevelOfDetail::BACKGROUND; - if (str == "light") - return MemoryDumpLevelOfDetail::LIGHT; - if (str == "detailed") - return MemoryDumpLevelOfDetail::DETAILED; - NOTREACHED(); - return MemoryDumpLevelOfDetail::LAST; -} - -MemoryDumpCallbackResult::MemoryDumpCallbackResult() {} - -MemoryDumpCallbackResult::~MemoryDumpCallbackResult() {} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_dump_request_args.h b/base/trace_event/memory_dump_request_args.h deleted file mode 100644 index a8b3f423cad37f4aaaac3b9f395e4f20b509a65a..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_request_args.h +++ /dev/null @@ -1,119 +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. - -#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_REQUEST_ARGS_H_ -#define BASE_TRACE_EVENT_MEMORY_DUMP_REQUEST_ARGS_H_ - -// This file defines the types and structs used to issue memory dump requests. -// These are also used in the IPCs for coordinating inter-process memory dumps. - -#include -#include -#include - -#include "base/base_export.h" -#include "base/callback.h" -#include "base/process/process_handle.h" - -namespace base { -namespace trace_event { - -// Captures the reason why a memory dump is being requested. This is to allow -// selective enabling of dumps, filtering and post-processing. Important: this -// must be kept consistent with -// services/resource_coordinator/public/cpp/memory/memory_infra_traits.cc. -enum class MemoryDumpType { - PERIODIC_INTERVAL, // Dumping memory at periodic intervals. - EXPLICITLY_TRIGGERED, // Non maskable dump request. - PEAK_MEMORY_USAGE, // Dumping memory at detected peak total memory usage. - LAST = PEAK_MEMORY_USAGE // For IPC macros. -}; - -// Tells the MemoryDumpProvider(s) how much detailed their dumps should be. -// Important: this must be kept consistent with -// services/resource_Coordinator/public/cpp/memory/memory_infra_traits.cc. -enum class MemoryDumpLevelOfDetail : uint32_t { - FIRST, - - // For background tracing mode. The dump time is quick, and typically just the - // totals are expected. Suballocations need not be specified. Dump name must - // contain only pre-defined strings and string arguments cannot be added. - BACKGROUND = FIRST, - - // For the levels below, MemoryDumpProvider instances must guarantee that the - // total size reported in the root node is consistent. Only the granularity of - // the child MemoryAllocatorDump(s) differs with the levels. - - // Few entries, typically a fixed number, per dump. - LIGHT, - - // Unrestricted amount of entries per dump. - DETAILED, - - LAST = DETAILED -}; - -// Initial request arguments for a global memory dump. (see -// MemoryDumpManager::RequestGlobalMemoryDump()). Important: this must be kept -// consistent with services/memory_infra/public/cpp/memory_infra_traits.cc. -struct BASE_EXPORT MemoryDumpRequestArgs { - // Globally unique identifier. In multi-process dumps, all processes issue a - // local dump with the same guid. This allows the trace importers to - // reconstruct the global dump. - uint64_t dump_guid; - - MemoryDumpType dump_type; - MemoryDumpLevelOfDetail level_of_detail; -}; - -// Args for ProcessMemoryDump and passed to OnMemoryDump calls for memory dump -// providers. Dump providers are expected to read the args for creating dumps. -struct MemoryDumpArgs { - // Specifies how detailed the dumps should be. - MemoryDumpLevelOfDetail level_of_detail; -}; - -// TODO(hjd): Not used yet, see crbug.com/703184 -// Summarises information about memory use as seen by a single process. -// This information will eventually be passed to a service to be colated -// and reported. -struct MemoryDumpCallbackResult { - struct OSMemDump { - uint32_t resident_set_kb = 0; - }; - struct ChromeMemDump { - uint32_t malloc_total_kb = 0; - uint32_t partition_alloc_total_kb = 0; - uint32_t blink_gc_total_kb = 0; - uint32_t v8_total_kb = 0; - }; - - // These are for the current process. - OSMemDump os_dump; - ChromeMemDump chrome_dump; - - // In some cases, OS stats can only be dumped from a privileged process to - // get around to sandboxing/selinux restrictions (see crbug.com/461788). - std::map extra_processes_dump; - - MemoryDumpCallbackResult(); - ~MemoryDumpCallbackResult(); -}; - -using MemoryDumpCallback = Callback; - -BASE_EXPORT const char* MemoryDumpTypeToString(const MemoryDumpType& dump_type); - -BASE_EXPORT MemoryDumpType StringToMemoryDumpType(const std::string& str); - -BASE_EXPORT const char* MemoryDumpLevelOfDetailToString( - const MemoryDumpLevelOfDetail& level_of_detail); - -BASE_EXPORT MemoryDumpLevelOfDetail -StringToMemoryDumpLevelOfDetail(const std::string& str); - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_MEMORY_DUMP_REQUEST_ARGS_H_ diff --git a/base/trace_event/memory_dump_scheduler.cc b/base/trace_event/memory_dump_scheduler.cc deleted file mode 100644 index 150feb8e791759681585c17318b05cc52ed639e7..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_scheduler.cc +++ /dev/null @@ -1,328 +0,0 @@ -// 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. - -#include "base/trace_event/memory_dump_scheduler.h" - -#include "base/process/process_metrics.h" -#include "base/single_thread_task_runner.h" -#include "base/threading/thread_task_runner_handle.h" -#include "base/trace_event/memory_dump_manager.h" -#include "build/build_config.h" - -namespace base { -namespace trace_event { - -namespace { -// Threshold on increase in memory from last dump beyond which a new dump must -// be triggered. -int64_t kDefaultMemoryIncreaseThreshold = 50 * 1024 * 1024; // 50MiB -const uint32_t kMemoryTotalsPollingInterval = 25; -uint32_t g_polling_interval_ms_for_testing = 0; -} // namespace - -// static -MemoryDumpScheduler* MemoryDumpScheduler::GetInstance() { - static MemoryDumpScheduler* instance = new MemoryDumpScheduler(); - return instance; -} - -MemoryDumpScheduler::MemoryDumpScheduler() : mdm_(nullptr), is_setup_(false) {} -MemoryDumpScheduler::~MemoryDumpScheduler() {} - -void MemoryDumpScheduler::Setup( - MemoryDumpManager* mdm, - scoped_refptr polling_task_runner) { - mdm_ = mdm; - polling_task_runner_ = polling_task_runner; - periodic_state_.reset(new PeriodicTriggerState); - polling_state_.reset(new PollingTriggerState); - is_setup_ = true; -} - -void MemoryDumpScheduler::AddTrigger(MemoryDumpType trigger_type, - MemoryDumpLevelOfDetail level_of_detail, - uint32_t min_time_between_dumps_ms) { - DCHECK(is_setup_); - if (trigger_type == MemoryDumpType::PEAK_MEMORY_USAGE) { - DCHECK(!periodic_state_->is_configured); - DCHECK_EQ(PollingTriggerState::DISABLED, polling_state_->current_state); - DCHECK_NE(0u, min_time_between_dumps_ms); - - polling_state_->level_of_detail = level_of_detail; - polling_state_->min_polls_between_dumps = - (min_time_between_dumps_ms + polling_state_->polling_interval_ms - 1) / - polling_state_->polling_interval_ms; - polling_state_->current_state = PollingTriggerState::CONFIGURED; - } else if (trigger_type == MemoryDumpType::PERIODIC_INTERVAL) { - DCHECK_EQ(PollingTriggerState::DISABLED, polling_state_->current_state); - periodic_state_->is_configured = true; - DCHECK_NE(0u, min_time_between_dumps_ms); - switch (level_of_detail) { - case MemoryDumpLevelOfDetail::BACKGROUND: - break; - case MemoryDumpLevelOfDetail::LIGHT: - DCHECK_EQ(0u, periodic_state_->light_dump_period_ms); - periodic_state_->light_dump_period_ms = min_time_between_dumps_ms; - break; - case MemoryDumpLevelOfDetail::DETAILED: - DCHECK_EQ(0u, periodic_state_->heavy_dump_period_ms); - periodic_state_->heavy_dump_period_ms = min_time_between_dumps_ms; - break; - } - - periodic_state_->min_timer_period_ms = std::min( - periodic_state_->min_timer_period_ms, min_time_between_dumps_ms); - DCHECK_EQ(0u, periodic_state_->light_dump_period_ms % - periodic_state_->min_timer_period_ms); - DCHECK_EQ(0u, periodic_state_->heavy_dump_period_ms % - periodic_state_->min_timer_period_ms); - } -} - -void MemoryDumpScheduler::EnablePeriodicTriggerIfNeeded() { - DCHECK(is_setup_); - if (!periodic_state_->is_configured || periodic_state_->timer.IsRunning()) - return; - periodic_state_->light_dumps_rate = periodic_state_->light_dump_period_ms / - periodic_state_->min_timer_period_ms; - periodic_state_->heavy_dumps_rate = periodic_state_->heavy_dump_period_ms / - periodic_state_->min_timer_period_ms; - - periodic_state_->dump_count = 0; - periodic_state_->timer.Start( - FROM_HERE, - TimeDelta::FromMilliseconds(periodic_state_->min_timer_period_ms), - Bind(&MemoryDumpScheduler::RequestPeriodicGlobalDump, Unretained(this))); -} - -void MemoryDumpScheduler::EnablePollingIfNeeded() { - DCHECK(is_setup_); - if (polling_state_->current_state != PollingTriggerState::CONFIGURED) - return; - - polling_state_->current_state = PollingTriggerState::ENABLED; - polling_state_->ResetTotals(); - - polling_task_runner_->PostTask( - FROM_HERE, - Bind(&MemoryDumpScheduler::PollMemoryOnPollingThread, Unretained(this))); -} - -void MemoryDumpScheduler::NotifyDumpTriggered() { - if (polling_task_runner_ && - !polling_task_runner_->RunsTasksOnCurrentThread()) { - polling_task_runner_->PostTask( - FROM_HERE, - Bind(&MemoryDumpScheduler::NotifyDumpTriggered, Unretained(this))); - return; - } - - if (!polling_state_ || - polling_state_->current_state != PollingTriggerState::ENABLED) { - return; - } - - polling_state_->ResetTotals(); -} - -void MemoryDumpScheduler::DisableAllTriggers() { - if (periodic_state_) { - if (periodic_state_->timer.IsRunning()) - periodic_state_->timer.Stop(); - periodic_state_.reset(); - } - - if (polling_task_runner_) { - DCHECK(polling_state_); - polling_task_runner_->PostTask( - FROM_HERE, Bind(&MemoryDumpScheduler::DisablePollingOnPollingThread, - Unretained(this))); - polling_task_runner_ = nullptr; - } - is_setup_ = false; -} - -void MemoryDumpScheduler::DisablePollingOnPollingThread() { - polling_state_->current_state = PollingTriggerState::DISABLED; - polling_state_.reset(); -} - -// static -void MemoryDumpScheduler::SetPollingIntervalForTesting(uint32_t interval) { - g_polling_interval_ms_for_testing = interval; -} - -bool MemoryDumpScheduler::IsPeriodicTimerRunningForTesting() { - return periodic_state_->timer.IsRunning(); -} - -void MemoryDumpScheduler::RequestPeriodicGlobalDump() { - MemoryDumpLevelOfDetail level_of_detail = MemoryDumpLevelOfDetail::BACKGROUND; - if (periodic_state_->light_dumps_rate > 0 && - periodic_state_->dump_count % periodic_state_->light_dumps_rate == 0) - level_of_detail = MemoryDumpLevelOfDetail::LIGHT; - if (periodic_state_->heavy_dumps_rate > 0 && - periodic_state_->dump_count % periodic_state_->heavy_dumps_rate == 0) - level_of_detail = MemoryDumpLevelOfDetail::DETAILED; - ++periodic_state_->dump_count; - - mdm_->RequestGlobalDump(MemoryDumpType::PERIODIC_INTERVAL, level_of_detail); -} - -void MemoryDumpScheduler::PollMemoryOnPollingThread() { - if (!polling_state_) - return; - - DCHECK_EQ(PollingTriggerState::ENABLED, polling_state_->current_state); - - uint64_t polled_memory = 0; - bool res = mdm_->PollFastMemoryTotal(&polled_memory); - DCHECK(res); - if (polling_state_->level_of_detail == MemoryDumpLevelOfDetail::DETAILED) { - TRACE_COUNTER1(MemoryDumpManager::kTraceCategory, "PolledMemoryMB", - polled_memory / 1024 / 1024); - } - - if (ShouldTriggerDump(polled_memory)) { - TRACE_EVENT_INSTANT1(MemoryDumpManager::kTraceCategory, - "Peak memory dump Triggered", - TRACE_EVENT_SCOPE_PROCESS, "total_usage_MB", - polled_memory / 1024 / 1024); - - mdm_->RequestGlobalDump(MemoryDumpType::PEAK_MEMORY_USAGE, - polling_state_->level_of_detail); - } - - // TODO(ssid): Use RequestSchedulerCallback, crbug.com/607533. - ThreadTaskRunnerHandle::Get()->PostDelayedTask( - FROM_HERE, - Bind(&MemoryDumpScheduler::PollMemoryOnPollingThread, Unretained(this)), - TimeDelta::FromMilliseconds(polling_state_->polling_interval_ms)); -} - -bool MemoryDumpScheduler::ShouldTriggerDump(uint64_t current_memory_total) { - // This function tries to detect peak memory usage as discussed in - // https://goo.gl/0kOU4A. - - if (current_memory_total == 0) - return false; - - bool should_dump = false; - ++polling_state_->num_polls_from_last_dump; - if (polling_state_->last_dump_memory_total == 0) { - // If it's first sample then trigger memory dump. - should_dump = true; - } else if (polling_state_->min_polls_between_dumps > - polling_state_->num_polls_from_last_dump) { - return false; - } - - int64_t increase_from_last_dump = - current_memory_total - polling_state_->last_dump_memory_total; - should_dump |= - increase_from_last_dump > polling_state_->memory_increase_threshold; - should_dump |= IsCurrentSamplePeak(current_memory_total); - if (should_dump) - polling_state_->ResetTotals(); - return should_dump; -} - -bool MemoryDumpScheduler::IsCurrentSamplePeak( - uint64_t current_memory_total_bytes) { - uint64_t current_memory_total_kb = current_memory_total_bytes / 1024; - polling_state_->last_memory_totals_kb_index = - (polling_state_->last_memory_totals_kb_index + 1) % - PollingTriggerState::kMaxNumMemorySamples; - uint64_t mean = 0; - for (uint32_t i = 0; i < PollingTriggerState::kMaxNumMemorySamples; ++i) { - if (polling_state_->last_memory_totals_kb[i] == 0) { - // Not enough samples to detect peaks. - polling_state_ - ->last_memory_totals_kb[polling_state_->last_memory_totals_kb_index] = - current_memory_total_kb; - return false; - } - mean += polling_state_->last_memory_totals_kb[i]; - } - mean = mean / PollingTriggerState::kMaxNumMemorySamples; - uint64_t variance = 0; - for (uint32_t i = 0; i < PollingTriggerState::kMaxNumMemorySamples; ++i) { - variance += (polling_state_->last_memory_totals_kb[i] - mean) * - (polling_state_->last_memory_totals_kb[i] - mean); - } - variance = variance / PollingTriggerState::kMaxNumMemorySamples; - - polling_state_ - ->last_memory_totals_kb[polling_state_->last_memory_totals_kb_index] = - current_memory_total_kb; - - // If stddev is less than 0.2% then we consider that the process is inactive. - bool is_stddev_low = variance < mean / 500 * mean / 500; - if (is_stddev_low) - return false; - - // (mean + 3.69 * stddev) corresponds to a value that is higher than current - // sample with 99.99% probability. - return (current_memory_total_kb - mean) * (current_memory_total_kb - mean) > - (3.69 * 3.69 * variance); -} - -MemoryDumpScheduler::PeriodicTriggerState::PeriodicTriggerState() - : is_configured(false), - dump_count(0), - min_timer_period_ms(std::numeric_limits::max()), - light_dumps_rate(0), - heavy_dumps_rate(0), - light_dump_period_ms(0), - heavy_dump_period_ms(0) {} - -MemoryDumpScheduler::PeriodicTriggerState::~PeriodicTriggerState() { - DCHECK(!timer.IsRunning()); -} - -MemoryDumpScheduler::PollingTriggerState::PollingTriggerState() - : current_state(DISABLED), - level_of_detail(MemoryDumpLevelOfDetail::FIRST), - polling_interval_ms(g_polling_interval_ms_for_testing - ? g_polling_interval_ms_for_testing - : kMemoryTotalsPollingInterval), - min_polls_between_dumps(0), - num_polls_from_last_dump(-1), - last_dump_memory_total(0), - memory_increase_threshold(0), - last_memory_totals_kb_index(0) {} - -MemoryDumpScheduler::PollingTriggerState::~PollingTriggerState() {} - -void MemoryDumpScheduler::PollingTriggerState::ResetTotals() { - if (!memory_increase_threshold) { - memory_increase_threshold = kDefaultMemoryIncreaseThreshold; -#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \ - defined(OS_ANDROID) - // Set threshold to 1% of total system memory. - SystemMemoryInfoKB meminfo; - bool res = GetSystemMemoryInfo(&meminfo); - if (res) { - memory_increase_threshold = - (static_cast(meminfo.total) / 100) * 1024; - } - DCHECK_GT(memory_increase_threshold, 0u); -#endif - } - - // Update the |last_dump_memory_total|'s value from the totals if it's not - // first poll. - if (num_polls_from_last_dump >= 0 && - last_memory_totals_kb[last_memory_totals_kb_index]) { - last_dump_memory_total = - last_memory_totals_kb[last_memory_totals_kb_index] * 1024; - } - num_polls_from_last_dump = 0; - for (uint32_t i = 0; i < kMaxNumMemorySamples; ++i) - last_memory_totals_kb[i] = 0; - last_memory_totals_kb_index = 0; -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_dump_scheduler.h b/base/trace_event/memory_dump_scheduler.h deleted file mode 100644 index ab8441bc20031242e5ac5df07f37eac12f66dc23..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_scheduler.h +++ /dev/null @@ -1,163 +0,0 @@ -// 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. - -#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_SCHEDULER_H -#define BASE_TRACE_EVENT_MEMORY_DUMP_SCHEDULER_H - -#include - -#include "base/base_export.h" -#include "base/gtest_prod_util.h" -#include "base/memory/ref_counted.h" -#include "base/timer/timer.h" -#include "base/trace_event/memory_dump_request_args.h" - -namespace base { -class SingleThreadTaskRunner; - -namespace trace_event { - -class MemoryDumpManager; - -// Schedules global dump requests based on the triggers added. The methods of -// this class are NOT thread safe and the client has to take care of invoking -// all the methods of the class safely. -class BASE_EXPORT MemoryDumpScheduler { - public: - static MemoryDumpScheduler* GetInstance(); - - // Initializes the scheduler. NOT thread safe. - void Setup(MemoryDumpManager* mdm_, - scoped_refptr polling_task_runner); - - // Adds triggers for scheduling global dumps. Both periodic and peak triggers - // cannot be added together. At the moment the periodic support is limited to - // at most one periodic trigger per dump mode and peak triggers are limited to - // at most one. All intervals should be an integeral multiple of the smallest - // interval specified. NOT thread safe. - void AddTrigger(MemoryDumpType trigger_type, - MemoryDumpLevelOfDetail level_of_detail, - uint32_t min_time_between_dumps_ms); - - // Starts periodic dumps. NOT thread safe and triggers must be added before - // enabling. - void EnablePeriodicTriggerIfNeeded(); - - // Starts polling memory total. NOT thread safe and triggers must be added - // before enabling. - void EnablePollingIfNeeded(); - - // Resets time for triggering dump to account for minimum time between the - // dumps. NOT thread safe. - void NotifyDumpTriggered(); - - // Disables all triggers. NOT thread safe. This should be called before - // polling thread is stopped to stop polling cleanly. - void DisableAllTriggers(); - - private: - friend class MemoryDumpManagerTest; - friend class MemoryDumpSchedulerPollingTest; - FRIEND_TEST_ALL_PREFIXES(MemoryDumpManagerTest, TestPollingOnDumpThread); - FRIEND_TEST_ALL_PREFIXES(MemoryDumpSchedulerPollingTest, NotifyDumpTriggered); - - // Helper class to schdule periodic memory dumps. - struct BASE_EXPORT PeriodicTriggerState { - PeriodicTriggerState(); - ~PeriodicTriggerState(); - - bool is_configured; - - RepeatingTimer timer; - uint32_t dump_count; - uint32_t min_timer_period_ms; - uint32_t light_dumps_rate; - uint32_t heavy_dumps_rate; - - uint32_t light_dump_period_ms; - uint32_t heavy_dump_period_ms; - - DISALLOW_COPY_AND_ASSIGN(PeriodicTriggerState); - }; - - struct BASE_EXPORT PollingTriggerState { - enum State { - CONFIGURED, // Polling trigger was added. - ENABLED, // Polling is running. - DISABLED // Polling is disabled. - }; - - static const uint32_t kMaxNumMemorySamples = 50; - - PollingTriggerState(); - ~PollingTriggerState(); - - // Helper to clear the tracked memory totals and poll count from last dump. - void ResetTotals(); - - State current_state; - MemoryDumpLevelOfDetail level_of_detail; - - uint32_t polling_interval_ms; - - // Minimum numer of polls after the last dump at which next dump can be - // triggered. - int min_polls_between_dumps; - int num_polls_from_last_dump; - - uint64_t last_dump_memory_total; - int64_t memory_increase_threshold; - uint64_t last_memory_totals_kb[kMaxNumMemorySamples]; - uint32_t last_memory_totals_kb_index; - - DISALLOW_COPY_AND_ASSIGN(PollingTriggerState); - }; - - MemoryDumpScheduler(); - ~MemoryDumpScheduler(); - - // Helper to set polling disabled. - void DisablePollingOnPollingThread(); - - // Periodically called by the timer. - void RequestPeriodicGlobalDump(); - - // Called for polling memory usage and trigger dumps if peak is detected. - void PollMemoryOnPollingThread(); - - // Returns true if peak memory value is detected. - bool ShouldTriggerDump(uint64_t current_memory_total); - - // Helper to detect peaks in memory usage. - bool IsCurrentSamplePeak(uint64_t current_memory_total); - - // Must be set before enabling tracing. - static void SetPollingIntervalForTesting(uint32_t interval); - - // True if periodic dumping is enabled. - bool IsPeriodicTimerRunningForTesting(); - - MemoryDumpManager* mdm_; - - // Accessed on the thread of the client before enabling and only accessed on - // the thread that called "EnablePeriodicTriggersIfNeeded()" after enabling. - std::unique_ptr periodic_state_; - - // Accessed on the thread of the client before enabling and only accessed on - // the polling thread after enabling. - std::unique_ptr polling_state_; - - // Accessed on the thread of the client only. - scoped_refptr polling_task_runner_; - - // True when the scheduler is setup. Accessed on the thread of client only. - bool is_setup_; - - DISALLOW_COPY_AND_ASSIGN(MemoryDumpScheduler); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_MEMORY_DUMP_SCHEDULER_H diff --git a/base/trace_event/memory_dump_scheduler_unittest.cc b/base/trace_event/memory_dump_scheduler_unittest.cc deleted file mode 100644 index 9af2a3b4306bf1b6ecf08b3cf66a417b61659ee5..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_scheduler_unittest.cc +++ /dev/null @@ -1,101 +0,0 @@ -// 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. - -#include "base/trace_event/memory_dump_scheduler.h" - -#include - -#include "base/single_thread_task_runner.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace trace_event { - -class MemoryDumpSchedulerPollingTest : public testing::Test { - public: - static const uint32_t kMinPollsToDump = 5; - - MemoryDumpSchedulerPollingTest() - : testing::Test(), - num_samples_tracked_( - MemoryDumpScheduler::PollingTriggerState::kMaxNumMemorySamples) {} - - void SetUp() override { - MemoryDumpScheduler::SetPollingIntervalForTesting(1); - uint32_t kMinPollsToDump = 5; - mds_ = MemoryDumpScheduler::GetInstance(); - mds_->Setup(nullptr, nullptr); - mds_->AddTrigger(MemoryDumpType::PEAK_MEMORY_USAGE, - MemoryDumpLevelOfDetail::LIGHT, kMinPollsToDump); - mds_->polling_state_->ResetTotals(); - mds_->polling_state_->current_state = - MemoryDumpScheduler::PollingTriggerState::ENABLED; - } - - void TearDown() override { - mds_->polling_state_->current_state = - MemoryDumpScheduler::PollingTriggerState::DISABLED; - } - - protected: - bool ShouldTriggerDump(uint64_t total) { - return mds_->ShouldTriggerDump(total); - } - - uint32_t num_samples_tracked_; - MemoryDumpScheduler* mds_; -}; - -TEST_F(MemoryDumpSchedulerPollingTest, PeakDetection) { - for (uint32_t i = 0; i < num_samples_tracked_ * 6; ++i) { - // Memory is increased in steps and dumps must be triggered at every step. - uint64_t total = (2 + (i / (2 * num_samples_tracked_))) * 1024 * 1204; - bool did_trigger = ShouldTriggerDump(total); - // Dumps must be triggered only at specific iterations. - bool should_have_triggered = i == 0; - should_have_triggered |= - (i > num_samples_tracked_) && (i % (2 * num_samples_tracked_) == 1); - if (should_have_triggered) { - ASSERT_TRUE(did_trigger) << "Dump wasn't triggered at " << i; - } else { - ASSERT_FALSE(did_trigger) << "Unexpected dump at " << i; - } - } -} - -TEST_F(MemoryDumpSchedulerPollingTest, SlowGrowthDetection) { - for (uint32_t i = 0; i < 15; ++i) { - // Record 1GiB of increase in each call. Dumps are triggered with 1% w.r.t - // system's total memory. - uint64_t total = static_cast(i + 1) * 1024 * 1024 * 1024; - bool did_trigger = ShouldTriggerDump(total); - bool should_have_triggered = i % kMinPollsToDump == 0; - if (should_have_triggered) { - ASSERT_TRUE(did_trigger) << "Dump wasn't triggered at " << i; - } else { - ASSERT_FALSE(did_trigger) << "Unexpected dump at " << i; - } - } -} - -TEST_F(MemoryDumpSchedulerPollingTest, NotifyDumpTriggered) { - for (uint32_t i = 0; i < num_samples_tracked_ * 6; ++i) { - uint64_t total = (2 + (i / (2 * num_samples_tracked_))) * 1024 * 1204; - if (i % num_samples_tracked_ == 0) - mds_->NotifyDumpTriggered(); - bool did_trigger = ShouldTriggerDump(total); - // Dumps should never be triggered since NotifyDumpTriggered() is called - // frequently. - EXPECT_NE(0u, mds_->polling_state_->last_dump_memory_total); - EXPECT_GT(num_samples_tracked_ - 1, - mds_->polling_state_->last_memory_totals_kb_index); - EXPECT_LT(static_cast( - total - mds_->polling_state_->last_dump_memory_total), - mds_->polling_state_->memory_increase_threshold); - ASSERT_FALSE(did_trigger && i) << "Unexpected dump at " << i; - } -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_dump_session_state.cc b/base/trace_event/memory_dump_session_state.cc deleted file mode 100644 index d26b82a5b74c65f774403eb7fae1cc88ba556f7f..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_session_state.cc +++ /dev/null @@ -1,37 +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. - -#include "base/trace_event/memory_dump_session_state.h" - -namespace base { -namespace trace_event { - -MemoryDumpSessionState::MemoryDumpSessionState() - : heap_profiler_breakdown_threshold_bytes_(0) {} -MemoryDumpSessionState::~MemoryDumpSessionState() {} - -void MemoryDumpSessionState::SetStackFrameDeduplicator( - std::unique_ptr stack_frame_deduplicator) { - DCHECK(!stack_frame_deduplicator_); - stack_frame_deduplicator_ = std::move(stack_frame_deduplicator); -} - -void MemoryDumpSessionState::SetTypeNameDeduplicator( - std::unique_ptr type_name_deduplicator) { - DCHECK(!type_name_deduplicator_); - type_name_deduplicator_ = std::move(type_name_deduplicator); -} - -void MemoryDumpSessionState::SetAllowedDumpModes( - std::set allowed_dump_modes) { - allowed_dump_modes_ = allowed_dump_modes; -} - -bool MemoryDumpSessionState::IsDumpModeAllowed( - MemoryDumpLevelOfDetail dump_mode) const { - return allowed_dump_modes_.count(dump_mode) != 0; -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_dump_session_state.h b/base/trace_event/memory_dump_session_state.h deleted file mode 100644 index 46092cb4832d7d8d69f51cfadd143760c26b0b15..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_dump_session_state.h +++ /dev/null @@ -1,77 +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. - -#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_SESSION_STATE_H_ -#define BASE_TRACE_EVENT_MEMORY_DUMP_SESSION_STATE_H_ - -#include -#include - -#include "base/base_export.h" -#include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" -#include "base/trace_event/heap_profiler_type_name_deduplicator.h" -#include "base/trace_event/memory_dump_request_args.h" - -namespace base { -namespace trace_event { - -// Container for state variables that should be shared across all the memory -// dumps in a tracing session. -class BASE_EXPORT MemoryDumpSessionState - : public RefCountedThreadSafe { - public: - MemoryDumpSessionState(); - - // Returns the stack frame deduplicator that should be used by memory dump - // providers when doing a heap dump. - StackFrameDeduplicator* stack_frame_deduplicator() const { - return stack_frame_deduplicator_.get(); - } - - void SetStackFrameDeduplicator( - std::unique_ptr stack_frame_deduplicator); - - // Returns the type name deduplicator that should be used by memory dump - // providers when doing a heap dump. - TypeNameDeduplicator* type_name_deduplicator() const { - return type_name_deduplicator_.get(); - } - - void SetTypeNameDeduplicator( - std::unique_ptr type_name_deduplicator); - - void SetAllowedDumpModes( - std::set allowed_dump_modes); - - bool IsDumpModeAllowed(MemoryDumpLevelOfDetail dump_mode) const; - - void set_heap_profiler_breakdown_threshold_bytes(uint32_t value) { - heap_profiler_breakdown_threshold_bytes_ = value; - } - - uint32_t heap_profiler_breakdown_threshold_bytes() const { - return heap_profiler_breakdown_threshold_bytes_; - } - - private: - friend class RefCountedThreadSafe; - ~MemoryDumpSessionState(); - - // Deduplicates backtraces in heap dumps so they can be written once when the - // trace is finalized. - std::unique_ptr stack_frame_deduplicator_; - - // Deduplicates type names in heap dumps so they can be written once when the - // trace is finalized. - std::unique_ptr type_name_deduplicator_; - - std::set allowed_dump_modes_; - - uint32_t heap_profiler_breakdown_threshold_bytes_; -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_MEMORY_DUMP_SESSION_STATE_H_ diff --git a/base/trace_event/memory_infra_background_whitelist.cc b/base/trace_event/memory_infra_background_whitelist.cc deleted file mode 100644 index 746068a7b1edf227af4153b16b6783b98378658e..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_infra_background_whitelist.cc +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright 2016 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. - -#include "base/trace_event/memory_infra_background_whitelist.h" - -#include -#include - -#include - -namespace base { -namespace trace_event { -namespace { - -// The names of dump providers whitelisted for background tracing. Dump -// providers can be added here only if the background mode dump has very -// less performance and memory overhead. -const char* const kDumpProviderWhitelist[] = { - "android::ResourceManagerImpl", - "BlinkGC", - "ClientDiscardableSharedMemoryManager", - "DOMStorage", - "DiscardableSharedMemoryManager", - "IndexedDBBackingStore", - "JavaHeap", - "LevelDB", - "LeveldbValueStore", - "Malloc", - "MemoryCache", - "PartitionAlloc", - "ProcessMemoryMetrics", - "Skia", - "Sql", - "URLRequestContext", - "V8Isolate", - "WinHeap", - "SyncDirectory", - "TabRestoreServiceHelper", - nullptr // End of list marker. -}; - -// A list of string names that are allowed for the memory allocator dumps in -// background mode. -const char* const kAllocatorDumpNameWhitelist[] = { - "blink_gc", - "blink_gc/allocated_objects", - "discardable", - "discardable/child_0x?", - "dom_storage/0x?/cache_size", - "dom_storage/session_storage_0x?", - "java_heap", - "java_heap/allocated_objects", - "leveldb/index_db/0x?", - "leveldb/leveldb_proto/0x?", - "leveldb/value_store/Extensions.Database.Open.Settings/0x?", - "leveldb/value_store/Extensions.Database.Open.Rules/0x?", - "leveldb/value_store/Extensions.Database.Open.State/0x?", - "leveldb/value_store/Extensions.Database.Open/0x?", - "leveldb/value_store/Extensions.Database.Restore/0x?", - "leveldb/value_store/Extensions.Database.Value.Restore/0x?", - "malloc", - "malloc/allocated_objects", - "malloc/metadata_fragmentation_caches", - "net/http_network_session_0x?", - "net/http_network_session_0x?/quic_stream_factory", - "net/http_network_session_0x?/socket_pool", - "net/http_network_session_0x?/spdy_session_pool", - "net/http_network_session_0x?/stream_factory", - "net/sdch_manager_0x?", - "net/ssl_session_cache", - "net/url_request_context", - "net/url_request_context/app_request", - "net/url_request_context/app_request/0x?", - "net/url_request_context/app_request/0x?/http_cache", - "net/url_request_context/app_request/0x?/http_cache/memory_backend", - "net/url_request_context/app_request/0x?/http_cache/simple_backend", - "net/url_request_context/app_request/0x?/http_network_session", - "net/url_request_context/app_request/0x?/sdch_manager", - "net/url_request_context/extensions", - "net/url_request_context/extensions/0x?", - "net/url_request_context/extensions/0x?/http_cache", - "net/url_request_context/extensions/0x?/http_cache/memory_backend", - "net/url_request_context/extensions/0x?/http_cache/simple_backend", - "net/url_request_context/extensions/0x?/http_network_session", - "net/url_request_context/extensions/0x?/sdch_manager", - "net/url_request_context/isolated_media", - "net/url_request_context/isolated_media/0x?", - "net/url_request_context/isolated_media/0x?/http_cache", - "net/url_request_context/isolated_media/0x?/http_cache/memory_backend", - "net/url_request_context/isolated_media/0x?/http_cache/simple_backend", - "net/url_request_context/isolated_media/0x?/http_network_session", - "net/url_request_context/isolated_media/0x?/sdch_manager", - "net/url_request_context/main", - "net/url_request_context/main/0x?", - "net/url_request_context/main/0x?/http_cache", - "net/url_request_context/main/0x?/http_cache/memory_backend", - "net/url_request_context/main/0x?/http_cache/simple_backend", - "net/url_request_context/main/0x?/http_network_session", - "net/url_request_context/main/0x?/sdch_manager", - "net/url_request_context/main_media", - "net/url_request_context/main_media/0x?", - "net/url_request_context/main_media/0x?/http_cache", - "net/url_request_context/main_media/0x?/http_cache/memory_backend", - "net/url_request_context/main_media/0x?/http_cache/simple_backend", - "net/url_request_context/main_media/0x?/http_network_session", - "net/url_request_context/main_media/0x?/sdch_manager", - "net/url_request_context/proxy", - "net/url_request_context/proxy/0x?", - "net/url_request_context/proxy/0x?/http_cache", - "net/url_request_context/proxy/0x?/http_cache/memory_backend", - "net/url_request_context/proxy/0x?/http_cache/simple_backend", - "net/url_request_context/proxy/0x?/http_network_session", - "net/url_request_context/proxy/0x?/sdch_manager", - "net/url_request_context/safe_browsing", - "net/url_request_context/safe_browsing/0x?", - "net/url_request_context/safe_browsing/0x?/http_cache", - "net/url_request_context/safe_browsing/0x?/http_cache/memory_backend", - "net/url_request_context/safe_browsing/0x?/http_cache/simple_backend", - "net/url_request_context/safe_browsing/0x?/http_network_session", - "net/url_request_context/safe_browsing/0x?/sdch_manager", - "net/url_request_context/system", - "net/url_request_context/system/0x?", - "net/url_request_context/system/0x?/http_cache", - "net/url_request_context/system/0x?/http_cache/memory_backend", - "net/url_request_context/system/0x?/http_cache/simple_backend", - "net/url_request_context/system/0x?/http_network_session", - "net/url_request_context/system/0x?/sdch_manager", - "net/url_request_context/unknown", - "net/url_request_context/unknown/0x?", - "net/url_request_context/unknown/0x?/http_cache", - "net/url_request_context/unknown/0x?/http_cache/memory_backend", - "net/url_request_context/unknown/0x?/http_cache/simple_backend", - "net/url_request_context/unknown/0x?/http_network_session", - "net/url_request_context/unknown/0x?/sdch_manager", - "web_cache/Image_resources", - "web_cache/CSS stylesheet_resources", - "web_cache/Script_resources", - "web_cache/XSL stylesheet_resources", - "web_cache/Font_resources", - "web_cache/Other_resources", - "partition_alloc/allocated_objects", - "partition_alloc/partitions", - "partition_alloc/partitions/array_buffer", - "partition_alloc/partitions/buffer", - "partition_alloc/partitions/fast_malloc", - "partition_alloc/partitions/layout", - "skia/sk_glyph_cache", - "skia/sk_resource_cache", - "sqlite", - "ui/resource_manager_0x?", - "v8/isolate_0x?/heap_spaces", - "v8/isolate_0x?/heap_spaces/code_space", - "v8/isolate_0x?/heap_spaces/large_object_space", - "v8/isolate_0x?/heap_spaces/map_space", - "v8/isolate_0x?/heap_spaces/new_space", - "v8/isolate_0x?/heap_spaces/old_space", - "v8/isolate_0x?/heap_spaces/other_spaces", - "v8/isolate_0x?/malloc", - "v8/isolate_0x?/zapped_for_debug", - "winheap", - "winheap/allocated_objects", - "sync/0x?/kernel", - "sync/0x?/store", - "sync/0x?/model_type/APP", - "sync/0x?/model_type/APP_LIST", - "sync/0x?/model_type/APP_NOTIFICATION", - "sync/0x?/model_type/APP_SETTING", - "sync/0x?/model_type/ARC_PACKAGE", - "sync/0x?/model_type/ARTICLE", - "sync/0x?/model_type/AUTOFILL", - "sync/0x?/model_type/AUTOFILL_PROFILE", - "sync/0x?/model_type/AUTOFILL_WALLET", - "sync/0x?/model_type/BOOKMARK", - "sync/0x?/model_type/DEVICE_INFO", - "sync/0x?/model_type/DICTIONARY", - "sync/0x?/model_type/EXPERIMENTS", - "sync/0x?/model_type/EXTENSION", - "sync/0x?/model_type/EXTENSION_SETTING", - "sync/0x?/model_type/FAVICON_IMAGE", - "sync/0x?/model_type/FAVICON_TRACKING", - "sync/0x?/model_type/HISTORY_DELETE_DIRECTIVE", - "sync/0x?/model_type/MANAGED_USER", - "sync/0x?/model_type/MANAGED_USER_SETTING", - "sync/0x?/model_type/MANAGED_USER_SHARED_SETTING", - "sync/0x?/model_type/MANAGED_USER_WHITELIST", - "sync/0x?/model_type/NIGORI", - "sync/0x?/model_type/PASSWORD", - "sync/0x?/model_type/PREFERENCE", - "sync/0x?/model_type/PRINTER", - "sync/0x?/model_type/PRIORITY_PREFERENCE", - "sync/0x?/model_type/READING_LIST", - "sync/0x?/model_type/SEARCH_ENGINE", - "sync/0x?/model_type/SESSION", - "sync/0x?/model_type/SYNCED_NOTIFICATION", - "sync/0x?/model_type/SYNCED_NOTIFICATION_APP_INFO", - "sync/0x?/model_type/THEME", - "sync/0x?/model_type/TYPED_URL", - "sync/0x?/model_type/WALLET_METADATA", - "sync/0x?/model_type/WIFI_CREDENTIAL", - "tab_restore/service_helper_0x?/entries", - "tab_restore/service_helper_0x?/entries/tab_0x?", - "tab_restore/service_helper_0x?/entries/window_0x?", - nullptr // End of list marker. -}; - -const char* const* g_dump_provider_whitelist = kDumpProviderWhitelist; -const char* const* g_allocator_dump_name_whitelist = - kAllocatorDumpNameWhitelist; - -} // namespace - -bool IsMemoryDumpProviderWhitelisted(const char* mdp_name) { - for (size_t i = 0; g_dump_provider_whitelist[i] != nullptr; ++i) { - if (strcmp(mdp_name, g_dump_provider_whitelist[i]) == 0) - return true; - } - return false; -} - -bool IsMemoryAllocatorDumpNameWhitelisted(const std::string& name) { - // Remove special characters, numbers (including hexadecimal which are marked - // by '0x') from the given string. - const size_t length = name.size(); - std::string stripped_str; - stripped_str.reserve(length); - bool parsing_hex = false; - for (size_t i = 0; i < length; ++i) { - if (parsing_hex && isxdigit(name[i])) - continue; - parsing_hex = false; - if (i + 1 < length && name[i] == '0' && name[i + 1] == 'x') { - parsing_hex = true; - stripped_str.append("0x?"); - ++i; - } else { - stripped_str.push_back(name[i]); - } - } - - for (size_t i = 0; g_allocator_dump_name_whitelist[i] != nullptr; ++i) { - if (stripped_str == g_allocator_dump_name_whitelist[i]) { - return true; - } - } - return false; -} - -void SetDumpProviderWhitelistForTesting(const char* const* list) { - g_dump_provider_whitelist = list; -} - -void SetAllocatorDumpNameWhitelistForTesting(const char* const* list) { - g_allocator_dump_name_whitelist = list; -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_infra_background_whitelist.h b/base/trace_event/memory_infra_background_whitelist.h deleted file mode 100644 index b8d704ae241c27d31d7e47fc4f4a4804590c8f04..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_infra_background_whitelist.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2016 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 BASE_TRACE_EVENT_MEMORY_INFRA_BACKGROUND_WHITELIST_H_ -#define BASE_TRACE_EVENT_MEMORY_INFRA_BACKGROUND_WHITELIST_H_ - -// This file contains the whitelists for background mode to limit the tracing -// overhead and remove sensitive information from traces. - -#include - -#include "base/base_export.h" - -namespace base { -namespace trace_event { - -// Checks if the given |mdp_name| is in the whitelist. -bool BASE_EXPORT IsMemoryDumpProviderWhitelisted(const char* mdp_name); - -// Checks if the given |name| matches any of the whitelisted patterns. -bool BASE_EXPORT IsMemoryAllocatorDumpNameWhitelisted(const std::string& name); - -// The whitelist is replaced with the given list for tests. The last element of -// the list must be nullptr. -void BASE_EXPORT SetDumpProviderWhitelistForTesting(const char* const* list); -void BASE_EXPORT -SetAllocatorDumpNameWhitelistForTesting(const char* const* list); - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_MEMORY_INFRA_BACKGROUND_WHITELIST_H_ diff --git a/base/trace_event/memory_usage_estimator.cc b/base/trace_event/memory_usage_estimator.cc deleted file mode 100644 index c769d5b6f1e5bc169e127508ba3bb47deb56e91d..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_usage_estimator.cc +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2016 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. - -#include "base/trace_event/memory_usage_estimator.h" - -namespace base { -namespace trace_event { - -template size_t EstimateMemoryUsage(const std::string&); -template size_t EstimateMemoryUsage(const string16&); - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/memory_usage_estimator.h b/base/trace_event/memory_usage_estimator.h deleted file mode 100644 index 6f02bb93bbbcd155764945f67ab5a1a6056dc6d5..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_usage_estimator.h +++ /dev/null @@ -1,549 +0,0 @@ -// Copyright 2016 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 BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_ -#define BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "base/base_export.h" -#include "base/containers/linked_list.h" -#include "base/strings/string16.h" -#include "base/template_util.h" - -// Composable memory usage estimators. -// -// This file defines set of EstimateMemoryUsage(object) functions that return -// approximate memory usage of their argument. -// -// The ultimate goal is to make memory usage estimation for a class simply a -// matter of aggregating EstimateMemoryUsage() results over all fields. -// -// That is achieved via composability: if EstimateMemoryUsage() is defined -// for T then EstimateMemoryUsage() is also defined for any combination of -// containers holding T (e.g. std::map>). -// -// There are two ways of defining EstimateMemoryUsage() for a type: -// -// 1. As a global function 'size_t EstimateMemoryUsage(T)' in -// in base::trace_event namespace. -// -// 2. As 'size_t T::EstimateMemoryUsage() const' method. In this case -// EstimateMemoryUsage(T) function in base::trace_event namespace is -// provided automatically. -// -// Here is an example implementation: -// -// size_t foo::bar::MyClass::EstimateMemoryUsage() const { -// return base::trace_event::EstimateMemoryUsage(name_) + -// base::trace_event::EstimateMemoryUsage(id_) + -// base::trace_event::EstimateMemoryUsage(items_); -// } -// -// The approach is simple: first call EstimateMemoryUsage() on all members, -// then recursively fix compilation errors that are caused by types not -// implementing EstimateMemoryUsage(). - -namespace base { -namespace trace_event { - -// Declarations - -// If T declares 'EstimateMemoryUsage() const' member function, then -// global function EstimateMemoryUsage(T) is available, and just calls -// the member function. -template -auto EstimateMemoryUsage(const T& object) - -> decltype(object.EstimateMemoryUsage()); - -// String - -template -size_t EstimateMemoryUsage(const std::basic_string& string); - -// Arrays - -template -size_t EstimateMemoryUsage(const std::array& array); - -template -size_t EstimateMemoryUsage(T (&array)[N]); - -template -size_t EstimateMemoryUsage(const T* array, size_t array_length); - -// std::unique_ptr - -template -size_t EstimateMemoryUsage(const std::unique_ptr& ptr); - -template -size_t EstimateMemoryUsage(const std::unique_ptr& array, - size_t array_length); - -// std::shared_ptr - -template -size_t EstimateMemoryUsage(const std::shared_ptr& ptr); - -// Containers - -template -size_t EstimateMemoryUsage(const std::pair& pair); - -template -size_t EstimateMemoryUsage(const std::vector& vector); - -template -size_t EstimateMemoryUsage(const std::list& list); - -template -size_t EstimateMemoryUsage(const base::LinkedList& list); - -template -size_t EstimateMemoryUsage(const std::set& set); - -template -size_t EstimateMemoryUsage(const std::multiset& set); - -template -size_t EstimateMemoryUsage(const std::map& map); - -template -size_t EstimateMemoryUsage(const std::multimap& map); - -template -size_t EstimateMemoryUsage(const std::unordered_set& set); - -template -size_t EstimateMemoryUsage(const std::unordered_multiset& set); - -template -size_t EstimateMemoryUsage(const std::unordered_map& map); - -template -size_t EstimateMemoryUsage(const std::unordered_multimap& map); - -template -size_t EstimateMemoryUsage(const std::deque& deque); - -template -size_t EstimateMemoryUsage(const std::queue& queue); - -template -size_t EstimateMemoryUsage(const std::priority_queue& queue); - -template -size_t EstimateMemoryUsage(const std::stack& stack); - -// TODO(dskiba): -// std::forward_list - -// Definitions - -namespace internal { - -// HasEMU::value is true iff EstimateMemoryUsage(T) is available. -// (This is the default version, which is false.) -template -struct HasEMU : std::false_type {}; - -// This HasEMU specialization is only picked up if there exists function -// EstimateMemoryUsage(const T&) that returns size_t. Simpler ways to -// achieve this don't work on MSVC. -template -struct HasEMU< - T, - typename std::enable_if()))>::value>::type> - : std::true_type {}; - -// EMUCaller does three things: -// 1. Defines Call() method that calls EstimateMemoryUsage(T) if it's -// available. -// 2. If EstimateMemoryUsage(T) is not available, but T has trivial dtor -// (i.e. it's POD, integer, pointer, enum, etc.) then it defines Call() -// method that returns 0. This is useful for containers, which allocate -// memory regardless of T (also for cases like std::map). -// 3. Finally, if EstimateMemoryUsage(T) is not available, then it triggers -// a static_assert with a helpful message. That cuts numbers of errors -// considerably - if you just call EstimateMemoryUsage(T) but it's not -// available for T, then compiler will helpfully list *all* possible -// variants of it, with an explanation for each. -template -struct EMUCaller { - // std::is_same<> below makes static_assert depend on T, in order to - // prevent it from asserting regardless instantiation. - static_assert(std::is_same::value, - "Neither global function 'size_t EstimateMemoryUsage(T)' " - "nor member function 'size_t T::EstimateMemoryUsage() const' " - "is defined for the type."); - - static size_t Call(const T&) { return 0; } -}; - -template -struct EMUCaller::value>::type> { - static size_t Call(const T& value) { return EstimateMemoryUsage(value); } -}; - -template -struct EMUCaller< - T, - typename std::enable_if::value && - is_trivially_destructible::value>::type> { - static size_t Call(const T& value) { return 0; } -}; - -// Returns reference to the underlying container of a container adapter. -// Works for std::stack, std::queue and std::priority_queue. -template -const typename A::container_type& GetUnderlyingContainer(const A& adapter) { - struct ExposedAdapter : A { - using A::c; - }; - return adapter.*&ExposedAdapter::c; -} - -} // namespace internal - -// Proxy that deducts T and calls EMUCaller. -// To be used by EstimateMemoryUsage() implementations for containers. -template -size_t EstimateItemMemoryUsage(const T& value) { - return internal::EMUCaller::Call(value); -} - -template -size_t EstimateIterableMemoryUsage(const I& iterable) { - size_t memory_usage = 0; - for (const auto& item : iterable) { - memory_usage += EstimateItemMemoryUsage(item); - } - return memory_usage; -} - -// Global EstimateMemoryUsage(T) that just calls T::EstimateMemoryUsage(). -template -auto EstimateMemoryUsage(const T& object) - -> decltype(object.EstimateMemoryUsage()) { - static_assert( - std::is_same::value, - "'T::EstimateMemoryUsage() const' must return size_t."); - return object.EstimateMemoryUsage(); -} - -// String - -template -size_t EstimateMemoryUsage(const std::basic_string& string) { - using string_type = std::basic_string; - using value_type = typename string_type::value_type; - // C++11 doesn't leave much room for implementors - std::string can - // use short string optimization, but that's about it. We detect SSO - // by checking that c_str() points inside |string|. - const uint8_t* cstr = reinterpret_cast(string.c_str()); - const uint8_t* inline_cstr = reinterpret_cast(&string); - if (cstr >= inline_cstr && cstr < inline_cstr + sizeof(string)) { - // SSO string - return 0; - } - return (string.capacity() + 1) * sizeof(value_type); -} - -// Use explicit instantiations from the .cc file (reduces bloat). -extern template BASE_EXPORT size_t EstimateMemoryUsage(const std::string&); -extern template BASE_EXPORT size_t EstimateMemoryUsage(const string16&); - -// Arrays - -template -size_t EstimateMemoryUsage(const std::array& array) { - return EstimateIterableMemoryUsage(array); -} - -template -size_t EstimateMemoryUsage(T (&array)[N]) { - return EstimateIterableMemoryUsage(array); -} - -template -size_t EstimateMemoryUsage(const T* array, size_t array_length) { - size_t memory_usage = sizeof(T) * array_length; - for (size_t i = 0; i != array_length; ++i) { - memory_usage += EstimateItemMemoryUsage(array[i]); - } - return memory_usage; -} - -// std::unique_ptr - -template -size_t EstimateMemoryUsage(const std::unique_ptr& ptr) { - return ptr ? (sizeof(T) + EstimateItemMemoryUsage(*ptr)) : 0; -} - -template -size_t EstimateMemoryUsage(const std::unique_ptr& array, - size_t array_length) { - return EstimateMemoryUsage(array.get(), array_length); -} - -// std::shared_ptr - -template -size_t EstimateMemoryUsage(const std::shared_ptr& ptr) { - auto use_count = ptr.use_count(); - if (use_count == 0) { - return 0; - } - // Model shared_ptr after libc++, - // see __shared_ptr_pointer from include/memory - struct SharedPointer { - void* vtbl; - long shared_owners; - long shared_weak_owners; - T* value; - }; - // If object of size S shared N > S times we prefer to (potentially) - // overestimate than to return 0. - return sizeof(SharedPointer) + - (EstimateItemMemoryUsage(*ptr) + (use_count - 1)) / use_count; -} - -// std::pair - -template -size_t EstimateMemoryUsage(const std::pair& pair) { - return EstimateItemMemoryUsage(pair.first) + - EstimateItemMemoryUsage(pair.second); -} - -// std::vector - -template -size_t EstimateMemoryUsage(const std::vector& vector) { - return sizeof(T) * vector.capacity() + EstimateIterableMemoryUsage(vector); -} - -// std::list - -template -size_t EstimateMemoryUsage(const std::list& list) { - using value_type = typename std::list::value_type; - struct Node { - Node* prev; - Node* next; - value_type value; - }; - return sizeof(Node) * list.size() + - EstimateIterableMemoryUsage(list); -} - -template -size_t EstimateMemoryUsage(const base::LinkedList& list) { - size_t memory_usage = 0u; - for (base::LinkNode* node = list.head(); node != list.end(); - node = node->next()) { - // Since we increment by calling node = node->next() we know that node - // isn't nullptr. - memory_usage += EstimateMemoryUsage(*node->value()) + sizeof(T); - } - return memory_usage; -} - -// Tree containers - -template -size_t EstimateTreeMemoryUsage(size_t size) { - // Tree containers are modeled after libc++ - // (__tree_node from include/__tree) - struct Node { - Node* left; - Node* right; - Node* parent; - bool is_black; - V value; - }; - return sizeof(Node) * size; -} - -template -size_t EstimateMemoryUsage(const std::set& set) { - using value_type = typename std::set::value_type; - return EstimateTreeMemoryUsage(set.size()) + - EstimateIterableMemoryUsage(set); -} - -template -size_t EstimateMemoryUsage(const std::multiset& set) { - using value_type = typename std::multiset::value_type; - return EstimateTreeMemoryUsage(set.size()) + - EstimateIterableMemoryUsage(set); -} - -template -size_t EstimateMemoryUsage(const std::map& map) { - using value_type = typename std::map::value_type; - return EstimateTreeMemoryUsage(map.size()) + - EstimateIterableMemoryUsage(map); -} - -template -size_t EstimateMemoryUsage(const std::multimap& map) { - using value_type = typename std::multimap::value_type; - return EstimateTreeMemoryUsage(map.size()) + - EstimateIterableMemoryUsage(map); -} - -// HashMap containers - -namespace internal { - -// While hashtable containers model doesn't depend on STL implementation, one -// detail still crept in: bucket_count. It's used in size estimation, but its -// value after inserting N items is not predictable. -// This function is specialized by unittests to return constant value, thus -// excluding bucket_count from testing. -template -size_t HashMapBucketCountForTesting(size_t bucket_count) { - return bucket_count; -} - -} // namespace internal - -template -size_t EstimateHashMapMemoryUsage(size_t bucket_count, size_t size) { - // Hashtable containers are modeled after libc++ - // (__hash_node from include/__hash_table) - struct Node { - void* next; - size_t hash; - V value; - }; - using Bucket = void*; - bucket_count = internal::HashMapBucketCountForTesting(bucket_count); - return sizeof(Bucket) * bucket_count + sizeof(Node) * size; -} - -template -size_t EstimateMemoryUsage(const std::unordered_set& set) { - using value_type = typename std::unordered_set::value_type; - return EstimateHashMapMemoryUsage(set.bucket_count(), - set.size()) + - EstimateIterableMemoryUsage(set); -} - -template -size_t EstimateMemoryUsage(const std::unordered_multiset& set) { - using value_type = typename std::unordered_multiset::value_type; - return EstimateHashMapMemoryUsage(set.bucket_count(), - set.size()) + - EstimateIterableMemoryUsage(set); -} - -template -size_t EstimateMemoryUsage(const std::unordered_map& map) { - using value_type = typename std::unordered_map::value_type; - return EstimateHashMapMemoryUsage(map.bucket_count(), - map.size()) + - EstimateIterableMemoryUsage(map); -} - -template -size_t EstimateMemoryUsage(const std::unordered_multimap& map) { - using value_type = - typename std::unordered_multimap::value_type; - return EstimateHashMapMemoryUsage(map.bucket_count(), - map.size()) + - EstimateIterableMemoryUsage(map); -} - -// std::deque - -template -size_t EstimateMemoryUsage(const std::deque& deque) { -// Since std::deque implementations are wildly different -// (see crbug.com/674287), we can't have one "good enough" -// way to estimate. - -// kBlockSize - minimum size of a block, in bytes -// kMinBlockLength - number of elements in a block -// if sizeof(T) > kBlockSize -#if defined(_LIBCPP_VERSION) - size_t kBlockSize = 4096; - size_t kMinBlockLength = 16; -#elif defined(__GLIBCXX__) - size_t kBlockSize = 512; - size_t kMinBlockLength = 1; -#elif defined(_MSC_VER) - size_t kBlockSize = 16; - size_t kMinBlockLength = 1; -#else - size_t kBlockSize = 0; - size_t kMinBlockLength = 1; -#endif - - size_t block_length = - (sizeof(T) > kBlockSize) ? kMinBlockLength : kBlockSize / sizeof(T); - - size_t blocks = (deque.size() + block_length - 1) / block_length; - -#if defined(__GLIBCXX__) - // libstdc++: deque always has at least one block - if (!blocks) - blocks = 1; -#endif - -#if defined(_LIBCPP_VERSION) - // libc++: deque keeps at most two blocks when it shrinks, - // so even if the size is zero, deque might be holding up - // to 4096 * 2 bytes. One way to know whether deque has - // ever allocated (and hence has 1 or 2 blocks) is to check - // iterator's pointer. Non-zero value means that deque has - // at least one block. - if (!blocks && deque.begin().operator->()) - blocks = 1; -#endif - - return (blocks * block_length * sizeof(T)) + - EstimateIterableMemoryUsage(deque); -} - -// Container adapters - -template -size_t EstimateMemoryUsage(const std::queue& queue) { - return EstimateMemoryUsage(internal::GetUnderlyingContainer(queue)); -} - -template -size_t EstimateMemoryUsage(const std::priority_queue& queue) { - return EstimateMemoryUsage(internal::GetUnderlyingContainer(queue)); -} - -template -size_t EstimateMemoryUsage(const std::stack& stack) { - return EstimateMemoryUsage(internal::GetUnderlyingContainer(stack)); -} - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_ diff --git a/base/trace_event/memory_usage_estimator_unittest.cc b/base/trace_event/memory_usage_estimator_unittest.cc deleted file mode 100644 index 80237c0192fa780acfa067a0461f9fe13b927b3d..0000000000000000000000000000000000000000 --- a/base/trace_event/memory_usage_estimator_unittest.cc +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2016 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. - -#include "base/trace_event/memory_usage_estimator.h" - -#include - -#include "base/memory/ptr_util.h" -#include "base/strings/string16.h" -#include "build/build_config.h" -#include "testing/gtest/include/gtest/gtest.h" - -#if defined(ARCH_CPU_64_BITS) -#define EXPECT_EQ_32_64(_, e, a) EXPECT_EQ(e, a) -#else -#define EXPECT_EQ_32_64(e, _, a) EXPECT_EQ(e, a) -#endif - -namespace base { -namespace trace_event { - -namespace { - -// Test class with predictable memory usage. -class Data { - public: - explicit Data(size_t size = 17): size_(size) { - } - - size_t size() const { return size_; } - - size_t EstimateMemoryUsage() const { - return size_; - } - - bool operator < (const Data& other) const { - return size_ < other.size_; - } - bool operator == (const Data& other) const { - return size_ == other.size_; - } - - struct Hasher { - size_t operator () (const Data& data) const { - return data.size(); - } - }; - - private: - size_t size_; -}; - -} // namespace - -namespace internal { - -// This kills variance of bucket_count across STL implementations. -template <> -size_t HashMapBucketCountForTesting(size_t) { - return 10; -} -template <> -size_t HashMapBucketCountForTesting>(size_t) { - return 10; -} - -} // namespace internal - -TEST(EstimateMemoryUsageTest, String) { - std::string string(777, 'a'); - EXPECT_EQ(string.capacity() + 1, EstimateMemoryUsage(string)); -} - -TEST(EstimateMemoryUsageTest, String16) { - string16 string(777, 'a'); - EXPECT_EQ(sizeof(char16) * (string.capacity() + 1), - EstimateMemoryUsage(string)); -} - -TEST(EstimateMemoryUsageTest, Arrays) { - // std::array - { - std::array array; - EXPECT_EQ(170u, EstimateMemoryUsage(array)); - } - - // T[N] - { - Data array[10]; - EXPECT_EQ(170u, EstimateMemoryUsage(array)); - } - - // C array - { - struct Item { - char payload[10]; - }; - Item* array = new Item[7]; - EXPECT_EQ(70u, EstimateMemoryUsage(array, 7)); - delete[] array; - } -} - -TEST(EstimateMemoryUsageTest, UniquePtr) { - // Empty - { - std::unique_ptr ptr; - EXPECT_EQ(0u, EstimateMemoryUsage(ptr)); - } - - // Not empty - { - std::unique_ptr ptr(new Data()); - EXPECT_EQ_32_64(21u, 25u, EstimateMemoryUsage(ptr)); - } - - // With a pointer - { - std::unique_ptr ptr(new Data*()); - EXPECT_EQ(sizeof(void*), EstimateMemoryUsage(ptr)); - } - - // With an array - { - struct Item { - uint32_t payload[10]; - }; - std::unique_ptr ptr(new Item[7]); - EXPECT_EQ(280u, EstimateMemoryUsage(ptr, 7)); - } -} - -TEST(EstimateMemoryUsageTest, Vector) { - std::vector vector; - vector.reserve(1000); - - // For an empty vector we should return memory usage of its buffer - size_t capacity = vector.capacity(); - size_t expected_size = capacity * sizeof(Data); - EXPECT_EQ(expected_size, EstimateMemoryUsage(vector)); - - // If vector is not empty, its size should also include memory usages - // of all elements. - for (size_t i = 0; i != capacity / 2; ++i) { - vector.push_back(Data(i)); - expected_size += EstimateMemoryUsage(vector.back()); - } - EXPECT_EQ(expected_size, EstimateMemoryUsage(vector)); -} - -TEST(EstimateMemoryUsageTest, List) { - struct POD { - short data; - }; - std::list list; - for (int i = 0; i != 1000; ++i) { - list.push_back(POD()); - } - EXPECT_EQ_32_64(12000u, 24000u, EstimateMemoryUsage(list)); -} - -TEST(EstimateMemoryUsageTest, Set) { - std::set> set; - for (int i = 0; i != 1000; ++i) { - set.insert({i, Data(i)}); - } - EXPECT_EQ_32_64(523500u, 547500u, EstimateMemoryUsage(set)); -} - -TEST(EstimateMemoryUsageTest, MultiSet) { - std::multiset set; - for (int i = 0; i != 1000; ++i) { - set.insert((i & 1) != 0); - } - EXPECT_EQ_32_64(16000u, 32000u, EstimateMemoryUsage(set)); -} - -TEST(EstimateMemoryUsageTest, Map) { - std::map map; - for (int i = 0; i != 1000; ++i) { - map.insert({Data(i), i}); - } - EXPECT_EQ_32_64(523500u, 547500u, EstimateMemoryUsage(map)); -} - -TEST(EstimateMemoryUsageTest, MultiMap) { - std::multimap map; - for (int i = 0; i != 1000; ++i) { - map.insert({static_cast(i), Data(i)}); - } - EXPECT_EQ_32_64(523500u, 547500u, EstimateMemoryUsage(map)); -} - -TEST(EstimateMemoryUsageTest, UnorderedSet) { - std::unordered_set set; - for (int i = 0; i != 1000; ++i) { - set.insert(Data(i)); - } - EXPECT_EQ_32_64(511540u, 523580u, EstimateMemoryUsage(set)); -} - -TEST(EstimateMemoryUsageTest, UnorderedMultiSet) { - std::unordered_multiset set; - for (int i = 0; i != 500; ++i) { - set.insert(Data(i)); - set.insert(Data(i)); - } - EXPECT_EQ_32_64(261540u, 273580u, EstimateMemoryUsage(set)); -} - -TEST(EstimateMemoryUsageTest, UnorderedMap) { - std::unordered_map map; - for (int i = 0; i != 1000; ++i) { - map.insert({Data(i), static_cast(i)}); - } - EXPECT_EQ_32_64(515540u, 531580u, EstimateMemoryUsage(map)); -} - -TEST(EstimateMemoryUsageTest, UnorderedMultiMap) { - std::unordered_multimap map; - for (int i = 0; i != 1000; ++i) { - map.insert({Data(i), static_cast(i)}); - } - EXPECT_EQ_32_64(515540u, 531580u, EstimateMemoryUsage(map)); -} - -TEST(EstimateMemoryUsageTest, Deque) { - std::deque deque; - - // Pick a large value so that platform-specific accounting - // for deque's blocks is small compared to usage of all items. - constexpr size_t kDataSize = 100000; - for (int i = 0; i != 1500; ++i) { - deque.push_back(Data(kDataSize)); - } - - // Compare against a reasonable minimum (i.e. no overhead). - size_t min_expected_usage = deque.size() * (sizeof(Data) + kDataSize); - EXPECT_LE(min_expected_usage, EstimateMemoryUsage(deque)); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/process_memory_dump.cc b/base/trace_event/process_memory_dump.cc deleted file mode 100644 index 63d1340e42e3222c3ee99e92a7e676279cffb8a1..0000000000000000000000000000000000000000 --- a/base/trace_event/process_memory_dump.cc +++ /dev/null @@ -1,367 +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. - -#include "base/trace_event/process_memory_dump.h" - -#include - -#include - -#include "base/memory/ptr_util.h" -#include "base/process/process_metrics.h" -#include "base/strings/stringprintf.h" -#include "base/trace_event/heap_profiler_heap_dump_writer.h" -#include "base/trace_event/memory_infra_background_whitelist.h" -#include "base/trace_event/process_memory_totals.h" -#include "base/trace_event/trace_event_argument.h" -#include "build/build_config.h" - -#if defined(OS_IOS) -#include -#endif - -#if defined(OS_POSIX) -#include -#endif - -#if defined(OS_WIN) -#include -#endif - -namespace base { -namespace trace_event { - -namespace { - -const char kEdgeTypeOwnership[] = "ownership"; - -std::string GetSharedGlobalAllocatorDumpName( - const MemoryAllocatorDumpGuid& guid) { - return "global/" + guid.ToString(); -} - -#if defined(COUNT_RESIDENT_BYTES_SUPPORTED) -size_t GetSystemPageCount(size_t mapped_size, size_t page_size) { - return (mapped_size + page_size - 1) / page_size; -} -#endif - -} // namespace - -// static -bool ProcessMemoryDump::is_black_hole_non_fatal_for_testing_ = false; - -#if defined(COUNT_RESIDENT_BYTES_SUPPORTED) -// static -size_t ProcessMemoryDump::GetSystemPageSize() { -#if defined(OS_IOS) - // On iOS, getpagesize() returns the user page sizes, but for allocating - // arrays for mincore(), kernel page sizes is needed. Use vm_kernel_page_size - // as recommended by Apple, https://forums.developer.apple.com/thread/47532/. - // Refer to http://crbug.com/542671 and Apple rdar://23651782 - return vm_kernel_page_size; -#else - return base::GetPageSize(); -#endif // defined(OS_IOS) -} - -// static -size_t ProcessMemoryDump::CountResidentBytes(void* start_address, - size_t mapped_size) { - const size_t page_size = GetSystemPageSize(); - const uintptr_t start_pointer = reinterpret_cast(start_address); - DCHECK_EQ(0u, start_pointer % page_size); - - size_t offset = 0; - size_t total_resident_size = 0; - bool failure = false; - - // An array as large as number of pages in memory segment needs to be passed - // to the query function. To avoid allocating a large array, the given block - // of memory is split into chunks of size |kMaxChunkSize|. - const size_t kMaxChunkSize = 8 * 1024 * 1024; - size_t max_vec_size = - GetSystemPageCount(std::min(mapped_size, kMaxChunkSize), page_size); -#if defined(OS_MACOSX) || defined(OS_IOS) - std::unique_ptr vec(new char[max_vec_size]); -#elif defined(OS_WIN) - std::unique_ptr vec( - new PSAPI_WORKING_SET_EX_INFORMATION[max_vec_size]); -#elif defined(OS_POSIX) - std::unique_ptr vec(new unsigned char[max_vec_size]); -#endif - - while (offset < mapped_size) { - uintptr_t chunk_start = (start_pointer + offset); - const size_t chunk_size = std::min(mapped_size - offset, kMaxChunkSize); - const size_t page_count = GetSystemPageCount(chunk_size, page_size); - size_t resident_page_count = 0; - -#if defined(OS_MACOSX) || defined(OS_IOS) - // mincore in MAC does not fail with EAGAIN. - failure = - !!mincore(reinterpret_cast(chunk_start), chunk_size, vec.get()); - for (size_t i = 0; i < page_count; i++) - resident_page_count += vec[i] & MINCORE_INCORE ? 1 : 0; -#elif defined(OS_WIN) - for (size_t i = 0; i < page_count; i++) { - vec[i].VirtualAddress = - reinterpret_cast(chunk_start + i * page_size); - } - DWORD vec_size = static_cast( - page_count * sizeof(PSAPI_WORKING_SET_EX_INFORMATION)); - failure = !QueryWorkingSetEx(GetCurrentProcess(), vec.get(), vec_size); - - for (size_t i = 0; i < page_count; i++) - resident_page_count += vec[i].VirtualAttributes.Valid; -#elif defined(OS_POSIX) - int error_counter = 0; - int result = 0; - // HANDLE_EINTR tries for 100 times. So following the same pattern. - do { - result = - mincore(reinterpret_cast(chunk_start), chunk_size, vec.get()); - } while (result == -1 && errno == EAGAIN && error_counter++ < 100); - failure = !!result; - - for (size_t i = 0; i < page_count; i++) - resident_page_count += vec[i] & 1; -#endif - - if (failure) - break; - - total_resident_size += resident_page_count * page_size; - offset += kMaxChunkSize; - } - - DCHECK(!failure); - if (failure) { - total_resident_size = 0; - LOG(ERROR) << "CountResidentBytes failed. The resident size is invalid"; - } - return total_resident_size; -} -#endif // defined(COUNT_RESIDENT_BYTES_SUPPORTED) - -ProcessMemoryDump::ProcessMemoryDump( - scoped_refptr session_state, - const MemoryDumpArgs& dump_args) - : has_process_totals_(false), - has_process_mmaps_(false), - session_state_(std::move(session_state)), - dump_args_(dump_args) {} - -ProcessMemoryDump::~ProcessMemoryDump() {} - -MemoryAllocatorDump* ProcessMemoryDump::CreateAllocatorDump( - const std::string& absolute_name) { - return AddAllocatorDumpInternal( - MakeUnique(absolute_name, this)); -} - -MemoryAllocatorDump* ProcessMemoryDump::CreateAllocatorDump( - const std::string& absolute_name, - const MemoryAllocatorDumpGuid& guid) { - return AddAllocatorDumpInternal( - MakeUnique(absolute_name, this, guid)); -} - -MemoryAllocatorDump* ProcessMemoryDump::AddAllocatorDumpInternal( - std::unique_ptr mad) { - // In background mode return the black hole dump, if invalid dump name is - // given. - if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND && - !IsMemoryAllocatorDumpNameWhitelisted(mad->absolute_name())) { - return GetBlackHoleMad(); - } - - auto insertion_result = allocator_dumps_.insert( - std::make_pair(mad->absolute_name(), std::move(mad))); - MemoryAllocatorDump* inserted_mad = insertion_result.first->second.get(); - DCHECK(insertion_result.second) << "Duplicate name: " - << inserted_mad->absolute_name(); - return inserted_mad; -} - -MemoryAllocatorDump* ProcessMemoryDump::GetAllocatorDump( - const std::string& absolute_name) const { - auto it = allocator_dumps_.find(absolute_name); - if (it != allocator_dumps_.end()) - return it->second.get(); - if (black_hole_mad_) - return black_hole_mad_.get(); - return nullptr; -} - -MemoryAllocatorDump* ProcessMemoryDump::GetOrCreateAllocatorDump( - const std::string& absolute_name) { - MemoryAllocatorDump* mad = GetAllocatorDump(absolute_name); - return mad ? mad : CreateAllocatorDump(absolute_name); -} - -MemoryAllocatorDump* ProcessMemoryDump::CreateSharedGlobalAllocatorDump( - const MemoryAllocatorDumpGuid& guid) { - // Global dumps are disabled in background mode. - if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND) - return GetBlackHoleMad(); - - // A shared allocator dump can be shared within a process and the guid could - // have been created already. - MemoryAllocatorDump* mad = GetSharedGlobalAllocatorDump(guid); - if (mad) { - // The weak flag is cleared because this method should create a non-weak - // dump. - mad->clear_flags(MemoryAllocatorDump::Flags::WEAK); - return mad; - } - return CreateAllocatorDump(GetSharedGlobalAllocatorDumpName(guid), guid); -} - -MemoryAllocatorDump* ProcessMemoryDump::CreateWeakSharedGlobalAllocatorDump( - const MemoryAllocatorDumpGuid& guid) { - // Global dumps are disabled in background mode. - if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND) - return GetBlackHoleMad(); - - MemoryAllocatorDump* mad = GetSharedGlobalAllocatorDump(guid); - if (mad) - return mad; - mad = CreateAllocatorDump(GetSharedGlobalAllocatorDumpName(guid), guid); - mad->set_flags(MemoryAllocatorDump::Flags::WEAK); - return mad; -} - -MemoryAllocatorDump* ProcessMemoryDump::GetSharedGlobalAllocatorDump( - const MemoryAllocatorDumpGuid& guid) const { - return GetAllocatorDump(GetSharedGlobalAllocatorDumpName(guid)); -} - -void ProcessMemoryDump::DumpHeapUsage( - const base::hash_map& metrics_by_context, - base::trace_event::TraceEventMemoryOverhead& overhead, - const char* allocator_name) { - if (!metrics_by_context.empty()) { - DCHECK_EQ(0ul, heap_dumps_.count(allocator_name)); - std::unique_ptr heap_dump = ExportHeapDump( - metrics_by_context, *session_state()); - heap_dumps_[allocator_name] = std::move(heap_dump); - } - - std::string base_name = base::StringPrintf("tracing/heap_profiler_%s", - allocator_name); - overhead.DumpInto(base_name.c_str(), this); -} - -void ProcessMemoryDump::Clear() { - if (has_process_totals_) { - process_totals_.Clear(); - has_process_totals_ = false; - } - - if (has_process_mmaps_) { - process_mmaps_.Clear(); - has_process_mmaps_ = false; - } - - allocator_dumps_.clear(); - allocator_dumps_edges_.clear(); - heap_dumps_.clear(); -} - -void ProcessMemoryDump::TakeAllDumpsFrom(ProcessMemoryDump* other) { - DCHECK(!other->has_process_totals() && !other->has_process_mmaps()); - - // Moves the ownership of all MemoryAllocatorDump(s) contained in |other| - // into this ProcessMemoryDump, checking for duplicates. - for (auto& it : other->allocator_dumps_) - AddAllocatorDumpInternal(std::move(it.second)); - other->allocator_dumps_.clear(); - - // Move all the edges. - allocator_dumps_edges_.insert(allocator_dumps_edges_.end(), - other->allocator_dumps_edges_.begin(), - other->allocator_dumps_edges_.end()); - other->allocator_dumps_edges_.clear(); - - for (auto& it : other->heap_dumps_) { - DCHECK_EQ(0ul, heap_dumps_.count(it.first)); - heap_dumps_.insert(std::make_pair(it.first, std::move(it.second))); - } - other->heap_dumps_.clear(); -} - -void ProcessMemoryDump::AsValueInto(TracedValue* value) const { - if (has_process_totals_) { - value->BeginDictionary("process_totals"); - process_totals_.AsValueInto(value); - value->EndDictionary(); - } - - if (has_process_mmaps_) { - value->BeginDictionary("process_mmaps"); - process_mmaps_.AsValueInto(value); - value->EndDictionary(); - } - - if (allocator_dumps_.size() > 0) { - value->BeginDictionary("allocators"); - for (const auto& allocator_dump_it : allocator_dumps_) - allocator_dump_it.second->AsValueInto(value); - value->EndDictionary(); - } - - if (heap_dumps_.size() > 0) { - value->BeginDictionary("heaps"); - for (const auto& name_and_dump : heap_dumps_) - value->SetValueWithCopiedName(name_and_dump.first, *name_and_dump.second); - value->EndDictionary(); // "heaps" - } - - value->BeginArray("allocators_graph"); - for (const MemoryAllocatorDumpEdge& edge : allocator_dumps_edges_) { - value->BeginDictionary(); - value->SetString("source", edge.source.ToString()); - value->SetString("target", edge.target.ToString()); - value->SetInteger("importance", edge.importance); - value->SetString("type", edge.type); - value->EndDictionary(); - } - value->EndArray(); -} - -void ProcessMemoryDump::AddOwnershipEdge(const MemoryAllocatorDumpGuid& source, - const MemoryAllocatorDumpGuid& target, - int importance) { - allocator_dumps_edges_.push_back( - {source, target, importance, kEdgeTypeOwnership}); -} - -void ProcessMemoryDump::AddOwnershipEdge( - const MemoryAllocatorDumpGuid& source, - const MemoryAllocatorDumpGuid& target) { - AddOwnershipEdge(source, target, 0 /* importance */); -} - -void ProcessMemoryDump::AddSuballocation(const MemoryAllocatorDumpGuid& source, - const std::string& target_node_name) { - // Do not create new dumps for suballocations in background mode. - if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND) - return; - - std::string child_mad_name = target_node_name + "/__" + source.ToString(); - MemoryAllocatorDump* target_child_mad = CreateAllocatorDump(child_mad_name); - AddOwnershipEdge(source, target_child_mad->guid()); -} - -MemoryAllocatorDump* ProcessMemoryDump::GetBlackHoleMad() { - DCHECK(is_black_hole_non_fatal_for_testing_); - if (!black_hole_mad_) - black_hole_mad_.reset(new MemoryAllocatorDump("discarded", this)); - return black_hole_mad_.get(); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/process_memory_dump.h b/base/trace_event/process_memory_dump.h deleted file mode 100644 index 6f8d167273327e2488ae30f12ca53928c810543d..0000000000000000000000000000000000000000 --- a/base/trace_event/process_memory_dump.h +++ /dev/null @@ -1,222 +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. - -#ifndef BASE_TRACE_EVENT_PROCESS_MEMORY_DUMP_H_ -#define BASE_TRACE_EVENT_PROCESS_MEMORY_DUMP_H_ - -#include - -#include -#include - -#include "base/base_export.h" -#include "base/macros.h" -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_vector.h" -#include "base/trace_event/memory_allocator_dump.h" -#include "base/trace_event/memory_allocator_dump_guid.h" -#include "base/trace_event/memory_dump_request_args.h" -#include "base/trace_event/memory_dump_session_state.h" -#include "base/trace_event/process_memory_maps.h" -#include "base/trace_event/process_memory_totals.h" -#include "build/build_config.h" - -// Define COUNT_RESIDENT_BYTES_SUPPORTED if platform supports counting of the -// resident memory. -#if (defined(OS_POSIX) && !defined(OS_NACL)) || defined(OS_WIN) -#define COUNT_RESIDENT_BYTES_SUPPORTED -#endif - -namespace base { -namespace trace_event { - -class MemoryDumpSessionState; -class TracedValue; - -// ProcessMemoryDump is as a strongly typed container which holds the dumps -// produced by the MemoryDumpProvider(s) for a specific process. -class BASE_EXPORT ProcessMemoryDump { - public: - struct MemoryAllocatorDumpEdge { - MemoryAllocatorDumpGuid source; - MemoryAllocatorDumpGuid target; - int importance; - const char* type; - }; - - // Maps allocator dumps absolute names (allocator_name/heap/subheap) to - // MemoryAllocatorDump instances. - using AllocatorDumpsMap = - std::unordered_map>; - - using HeapDumpsMap = - std::unordered_map>; - -#if defined(COUNT_RESIDENT_BYTES_SUPPORTED) - // Returns the number of bytes in a kernel memory page. Some platforms may - // have a different value for kernel page sizes from user page sizes. It is - // important to use kernel memory page sizes for resident bytes calculation. - // In most cases, the two are the same. - static size_t GetSystemPageSize(); - - // Returns the total bytes resident for a virtual address range, with given - // |start_address| and |mapped_size|. |mapped_size| is specified in bytes. The - // value returned is valid only if the given range is currently mmapped by the - // process. The |start_address| must be page-aligned. - static size_t CountResidentBytes(void* start_address, size_t mapped_size); -#endif - - ProcessMemoryDump(scoped_refptr session_state, - const MemoryDumpArgs& dump_args); - ~ProcessMemoryDump(); - - // Creates a new MemoryAllocatorDump with the given name and returns the - // empty object back to the caller. - // Arguments: - // absolute_name: a name that uniquely identifies allocator dumps produced - // by this provider. It is possible to specify nesting by using a - // path-like string (e.g., v8/isolate1/heap1, v8/isolate1/heap2). - // Leading or trailing slashes are not allowed. - // guid: an optional identifier, unique among all processes within the - // scope of a global dump. This is only relevant when using - // AddOwnershipEdge() to express memory sharing. If omitted, - // it will be automatically generated. - // ProcessMemoryDump handles the memory ownership of its MemoryAllocatorDumps. - MemoryAllocatorDump* CreateAllocatorDump(const std::string& absolute_name); - MemoryAllocatorDump* CreateAllocatorDump(const std::string& absolute_name, - const MemoryAllocatorDumpGuid& guid); - - // Looks up a MemoryAllocatorDump given its allocator and heap names, or - // nullptr if not found. - MemoryAllocatorDump* GetAllocatorDump(const std::string& absolute_name) const; - - MemoryAllocatorDump* GetOrCreateAllocatorDump( - const std::string& absolute_name); - - // Creates a shared MemoryAllocatorDump, to express cross-process sharing. - // Shared allocator dumps are allowed to have duplicate guids within the - // global scope, in order to reference the same dump from multiple processes. - // See the design doc goo.gl/keU6Bf for reference usage patterns. - MemoryAllocatorDump* CreateSharedGlobalAllocatorDump( - const MemoryAllocatorDumpGuid& guid); - - // Creates a shared MemoryAllocatorDump as CreateSharedGlobalAllocatorDump, - // but with a WEAK flag. A weak dump will be discarded unless a non-weak dump - // is created using CreateSharedGlobalAllocatorDump by at least one process. - // The WEAK flag does not apply if a non-weak dump with the same GUID already - // exists or is created later. All owners and children of the discarded dump - // will also be discarded transitively. - MemoryAllocatorDump* CreateWeakSharedGlobalAllocatorDump( - const MemoryAllocatorDumpGuid& guid); - - // Looks up a shared MemoryAllocatorDump given its guid. - MemoryAllocatorDump* GetSharedGlobalAllocatorDump( - const MemoryAllocatorDumpGuid& guid) const; - - // Returns the map of the MemoryAllocatorDumps added to this dump. - const AllocatorDumpsMap& allocator_dumps() const { return allocator_dumps_; } - - // Dumps heap usage with |allocator_name|. - void DumpHeapUsage(const base::hash_map& - metrics_by_context, - base::trace_event::TraceEventMemoryOverhead& overhead, - const char* allocator_name); - - // Adds an ownership relationship between two MemoryAllocatorDump(s) with the - // semantics: |source| owns |target|, and has the effect of attributing - // the memory usage of |target| to |source|. |importance| is optional and - // relevant only for the cases of co-ownership, where it acts as a z-index: - // the owner with the highest importance will be attributed |target|'s memory. - void AddOwnershipEdge(const MemoryAllocatorDumpGuid& source, - const MemoryAllocatorDumpGuid& target, - int importance); - void AddOwnershipEdge(const MemoryAllocatorDumpGuid& source, - const MemoryAllocatorDumpGuid& target); - - const std::vector& allocator_dumps_edges() const { - return allocator_dumps_edges_; - } - - // Utility method to add a suballocation relationship with the following - // semantics: |source| is suballocated from |target_node_name|. - // This creates a child node of |target_node_name| and adds an ownership edge - // between |source| and the new child node. As a result, the UI will not - // account the memory of |source| in the target node. - void AddSuballocation(const MemoryAllocatorDumpGuid& source, - const std::string& target_node_name); - - const scoped_refptr& session_state() const { - return session_state_; - } - - // Removes all the MemoryAllocatorDump(s) contained in this instance. This - // ProcessMemoryDump can be safely reused as if it was new once this returns. - void Clear(); - - // Merges all MemoryAllocatorDump(s) contained in |other| inside this - // ProcessMemoryDump, transferring their ownership to this instance. - // |other| will be an empty ProcessMemoryDump after this method returns. - // This is to allow dump providers to pre-populate ProcessMemoryDump instances - // and later move their contents into the ProcessMemoryDump passed as argument - // of the MemoryDumpProvider::OnMemoryDump(ProcessMemoryDump*) callback. - void TakeAllDumpsFrom(ProcessMemoryDump* other); - - // Called at trace generation time to populate the TracedValue. - void AsValueInto(TracedValue* value) const; - - ProcessMemoryTotals* process_totals() { return &process_totals_; } - bool has_process_totals() const { return has_process_totals_; } - void set_has_process_totals() { has_process_totals_ = true; } - - ProcessMemoryMaps* process_mmaps() { return &process_mmaps_; } - bool has_process_mmaps() const { return has_process_mmaps_; } - void set_has_process_mmaps() { has_process_mmaps_ = true; } - - const HeapDumpsMap& heap_dumps() const { return heap_dumps_; } - - const MemoryDumpArgs& dump_args() const { return dump_args_; } - - private: - FRIEND_TEST_ALL_PREFIXES(ProcessMemoryDumpTest, BackgroundModeTest); - - MemoryAllocatorDump* AddAllocatorDumpInternal( - std::unique_ptr mad); - - MemoryAllocatorDump* GetBlackHoleMad(); - - ProcessMemoryTotals process_totals_; - bool has_process_totals_; - - ProcessMemoryMaps process_mmaps_; - bool has_process_mmaps_; - - AllocatorDumpsMap allocator_dumps_; - HeapDumpsMap heap_dumps_; - - // State shared among all PMDs instances created in a given trace session. - scoped_refptr session_state_; - - // Keeps track of relationships between MemoryAllocatorDump(s). - std::vector allocator_dumps_edges_; - - // Level of detail of the current dump. - const MemoryDumpArgs dump_args_; - - // This allocator dump is returned when an invalid dump is created in - // background mode. The attributes of the dump are ignored and not added to - // the trace. - std::unique_ptr black_hole_mad_; - - // When set to true, the DCHECK(s) for invalid dump creations on the - // background mode are disabled for testing. - static bool is_black_hole_non_fatal_for_testing_; - - DISALLOW_COPY_AND_ASSIGN(ProcessMemoryDump); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_PROCESS_MEMORY_DUMP_H_ diff --git a/base/trace_event/process_memory_dump_unittest.cc b/base/trace_event/process_memory_dump_unittest.cc deleted file mode 100644 index 571774a10ca2224e469e447902be2e0b5618953b..0000000000000000000000000000000000000000 --- a/base/trace_event/process_memory_dump_unittest.cc +++ /dev/null @@ -1,306 +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. - -#include "base/trace_event/process_memory_dump.h" - -#include - -#include "base/memory/aligned_memory.h" -#include "base/memory/ptr_util.h" -#include "base/process/process_metrics.h" -#include "base/trace_event/memory_allocator_dump_guid.h" -#include "base/trace_event/memory_infra_background_whitelist.h" -#include "base/trace_event/trace_event_argument.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace trace_event { - -namespace { - -const MemoryDumpArgs kDetailedDumpArgs = {MemoryDumpLevelOfDetail::DETAILED}; -const char* const kTestDumpNameWhitelist[] = { - "Whitelisted/TestName", "Whitelisted/TestName_0x?", - "Whitelisted/0x?/TestName", nullptr}; - -TracedValue* GetHeapDump(const ProcessMemoryDump& pmd, const char* name) { - auto it = pmd.heap_dumps().find(name); - return it == pmd.heap_dumps().end() ? nullptr : it->second.get(); -} - -} // namespace - -TEST(ProcessMemoryDumpTest, Clear) { - std::unique_ptr pmd1( - new ProcessMemoryDump(nullptr, kDetailedDumpArgs)); - pmd1->CreateAllocatorDump("mad1"); - pmd1->CreateAllocatorDump("mad2"); - ASSERT_FALSE(pmd1->allocator_dumps().empty()); - - pmd1->process_totals()->set_resident_set_bytes(42); - pmd1->set_has_process_totals(); - - pmd1->process_mmaps()->AddVMRegion(ProcessMemoryMaps::VMRegion()); - pmd1->set_has_process_mmaps(); - - pmd1->AddOwnershipEdge(MemoryAllocatorDumpGuid(42), - MemoryAllocatorDumpGuid(4242)); - - MemoryAllocatorDumpGuid shared_mad_guid1(1); - MemoryAllocatorDumpGuid shared_mad_guid2(2); - pmd1->CreateSharedGlobalAllocatorDump(shared_mad_guid1); - pmd1->CreateSharedGlobalAllocatorDump(shared_mad_guid2); - - pmd1->Clear(); - ASSERT_TRUE(pmd1->allocator_dumps().empty()); - ASSERT_TRUE(pmd1->allocator_dumps_edges().empty()); - ASSERT_EQ(nullptr, pmd1->GetAllocatorDump("mad1")); - ASSERT_EQ(nullptr, pmd1->GetAllocatorDump("mad2")); - ASSERT_FALSE(pmd1->has_process_totals()); - ASSERT_FALSE(pmd1->has_process_mmaps()); - ASSERT_TRUE(pmd1->process_mmaps()->vm_regions().empty()); - ASSERT_EQ(nullptr, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid1)); - ASSERT_EQ(nullptr, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid2)); - - // Check that calling AsValueInto() doesn't cause a crash. - std::unique_ptr traced_value(new TracedValue); - pmd1->AsValueInto(traced_value.get()); - - // Check that the pmd can be reused and behaves as expected. - auto* mad1 = pmd1->CreateAllocatorDump("mad1"); - auto* mad3 = pmd1->CreateAllocatorDump("mad3"); - auto* shared_mad1 = pmd1->CreateSharedGlobalAllocatorDump(shared_mad_guid1); - auto* shared_mad2 = - pmd1->CreateWeakSharedGlobalAllocatorDump(shared_mad_guid2); - ASSERT_EQ(4u, pmd1->allocator_dumps().size()); - ASSERT_EQ(mad1, pmd1->GetAllocatorDump("mad1")); - ASSERT_EQ(nullptr, pmd1->GetAllocatorDump("mad2")); - ASSERT_EQ(mad3, pmd1->GetAllocatorDump("mad3")); - ASSERT_EQ(shared_mad1, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid1)); - ASSERT_EQ(MemoryAllocatorDump::Flags::DEFAULT, shared_mad1->flags()); - ASSERT_EQ(shared_mad2, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid2)); - ASSERT_EQ(MemoryAllocatorDump::Flags::WEAK, shared_mad2->flags()); - - traced_value.reset(new TracedValue); - pmd1->AsValueInto(traced_value.get()); - - pmd1.reset(); -} - -TEST(ProcessMemoryDumpTest, TakeAllDumpsFrom) { - std::unique_ptr traced_value(new TracedValue); - hash_map metrics_by_context; - metrics_by_context[AllocationContext()] = { 1, 1 }; - TraceEventMemoryOverhead overhead; - - scoped_refptr session_state = - new MemoryDumpSessionState; - session_state->SetStackFrameDeduplicator( - WrapUnique(new StackFrameDeduplicator)); - session_state->SetTypeNameDeduplicator( - WrapUnique(new TypeNameDeduplicator)); - std::unique_ptr pmd1( - new ProcessMemoryDump(session_state.get(), kDetailedDumpArgs)); - auto* mad1_1 = pmd1->CreateAllocatorDump("pmd1/mad1"); - auto* mad1_2 = pmd1->CreateAllocatorDump("pmd1/mad2"); - pmd1->AddOwnershipEdge(mad1_1->guid(), mad1_2->guid()); - pmd1->DumpHeapUsage(metrics_by_context, overhead, "pmd1/heap_dump1"); - pmd1->DumpHeapUsage(metrics_by_context, overhead, "pmd1/heap_dump2"); - - std::unique_ptr pmd2( - new ProcessMemoryDump(session_state.get(), kDetailedDumpArgs)); - auto* mad2_1 = pmd2->CreateAllocatorDump("pmd2/mad1"); - auto* mad2_2 = pmd2->CreateAllocatorDump("pmd2/mad2"); - pmd2->AddOwnershipEdge(mad2_1->guid(), mad2_2->guid()); - pmd2->DumpHeapUsage(metrics_by_context, overhead, "pmd2/heap_dump1"); - pmd2->DumpHeapUsage(metrics_by_context, overhead, "pmd2/heap_dump2"); - - MemoryAllocatorDumpGuid shared_mad_guid1(1); - MemoryAllocatorDumpGuid shared_mad_guid2(2); - auto* shared_mad1 = pmd2->CreateSharedGlobalAllocatorDump(shared_mad_guid1); - auto* shared_mad2 = - pmd2->CreateWeakSharedGlobalAllocatorDump(shared_mad_guid2); - - pmd1->TakeAllDumpsFrom(pmd2.get()); - - // Make sure that pmd2 is empty but still usable after it has been emptied. - ASSERT_TRUE(pmd2->allocator_dumps().empty()); - ASSERT_TRUE(pmd2->allocator_dumps_edges().empty()); - ASSERT_TRUE(pmd2->heap_dumps().empty()); - pmd2->CreateAllocatorDump("pmd2/this_mad_stays_with_pmd2"); - ASSERT_EQ(1u, pmd2->allocator_dumps().size()); - ASSERT_EQ(1u, pmd2->allocator_dumps().count("pmd2/this_mad_stays_with_pmd2")); - pmd2->AddOwnershipEdge(MemoryAllocatorDumpGuid(42), - MemoryAllocatorDumpGuid(4242)); - - // Check that calling AsValueInto() doesn't cause a crash. - pmd2->AsValueInto(traced_value.get()); - - // Free the |pmd2| to check that the memory ownership of the two MAD(s) - // has been transferred to |pmd1|. - pmd2.reset(); - - // Now check that |pmd1| has been effectively merged. - ASSERT_EQ(6u, pmd1->allocator_dumps().size()); - ASSERT_EQ(1u, pmd1->allocator_dumps().count("pmd1/mad1")); - ASSERT_EQ(1u, pmd1->allocator_dumps().count("pmd1/mad2")); - ASSERT_EQ(1u, pmd1->allocator_dumps().count("pmd2/mad1")); - ASSERT_EQ(1u, pmd1->allocator_dumps().count("pmd1/mad2")); - ASSERT_EQ(2u, pmd1->allocator_dumps_edges().size()); - ASSERT_EQ(shared_mad1, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid1)); - ASSERT_EQ(shared_mad2, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid2)); - ASSERT_TRUE(MemoryAllocatorDump::Flags::WEAK & shared_mad2->flags()); - ASSERT_EQ(4u, pmd1->heap_dumps().size()); - ASSERT_TRUE(GetHeapDump(*pmd1, "pmd1/heap_dump1") != nullptr); - ASSERT_TRUE(GetHeapDump(*pmd1, "pmd1/heap_dump2") != nullptr); - ASSERT_TRUE(GetHeapDump(*pmd1, "pmd2/heap_dump1") != nullptr); - ASSERT_TRUE(GetHeapDump(*pmd1, "pmd2/heap_dump2") != nullptr); - - // Check that calling AsValueInto() doesn't cause a crash. - traced_value.reset(new TracedValue); - pmd1->AsValueInto(traced_value.get()); - - pmd1.reset(); -} - -TEST(ProcessMemoryDumpTest, Suballocations) { - std::unique_ptr pmd( - new ProcessMemoryDump(nullptr, kDetailedDumpArgs)); - const std::string allocator_dump_name = "fakealloc/allocated_objects"; - pmd->CreateAllocatorDump(allocator_dump_name); - - // Create one allocation with an auto-assigned guid and mark it as a - // suballocation of "fakealloc/allocated_objects". - auto* pic1_dump = pmd->CreateAllocatorDump("picturemanager/picture1"); - pmd->AddSuballocation(pic1_dump->guid(), allocator_dump_name); - - // Same here, but this time create an allocation with an explicit guid. - auto* pic2_dump = pmd->CreateAllocatorDump("picturemanager/picture2", - MemoryAllocatorDumpGuid(0x42)); - pmd->AddSuballocation(pic2_dump->guid(), allocator_dump_name); - - // Now check that AddSuballocation() has created anonymous child dumps under - // "fakealloc/allocated_objects". - auto anon_node_1_it = pmd->allocator_dumps().find( - allocator_dump_name + "/__" + pic1_dump->guid().ToString()); - ASSERT_NE(pmd->allocator_dumps().end(), anon_node_1_it); - - auto anon_node_2_it = - pmd->allocator_dumps().find(allocator_dump_name + "/__42"); - ASSERT_NE(pmd->allocator_dumps().end(), anon_node_2_it); - - // Finally check that AddSuballocation() has created also the - // edges between the pictures and the anonymous allocator child dumps. - bool found_edge[2]{false, false}; - for (const auto& e : pmd->allocator_dumps_edges()) { - found_edge[0] |= (e.source == pic1_dump->guid() && - e.target == anon_node_1_it->second->guid()); - found_edge[1] |= (e.source == pic2_dump->guid() && - e.target == anon_node_2_it->second->guid()); - } - ASSERT_TRUE(found_edge[0]); - ASSERT_TRUE(found_edge[1]); - - // Check that calling AsValueInto() doesn't cause a crash. - std::unique_ptr traced_value(new TracedValue); - pmd->AsValueInto(traced_value.get()); - - pmd.reset(); -} - -TEST(ProcessMemoryDumpTest, GlobalAllocatorDumpTest) { - std::unique_ptr pmd( - new ProcessMemoryDump(nullptr, kDetailedDumpArgs)); - MemoryAllocatorDumpGuid shared_mad_guid(1); - auto* shared_mad1 = pmd->CreateWeakSharedGlobalAllocatorDump(shared_mad_guid); - ASSERT_EQ(shared_mad_guid, shared_mad1->guid()); - ASSERT_EQ(MemoryAllocatorDump::Flags::WEAK, shared_mad1->flags()); - - auto* shared_mad2 = pmd->GetSharedGlobalAllocatorDump(shared_mad_guid); - ASSERT_EQ(shared_mad1, shared_mad2); - ASSERT_EQ(MemoryAllocatorDump::Flags::WEAK, shared_mad1->flags()); - - auto* shared_mad3 = pmd->CreateWeakSharedGlobalAllocatorDump(shared_mad_guid); - ASSERT_EQ(shared_mad1, shared_mad3); - ASSERT_EQ(MemoryAllocatorDump::Flags::WEAK, shared_mad1->flags()); - - auto* shared_mad4 = pmd->CreateSharedGlobalAllocatorDump(shared_mad_guid); - ASSERT_EQ(shared_mad1, shared_mad4); - ASSERT_EQ(MemoryAllocatorDump::Flags::DEFAULT, shared_mad1->flags()); - - auto* shared_mad5 = pmd->CreateWeakSharedGlobalAllocatorDump(shared_mad_guid); - ASSERT_EQ(shared_mad1, shared_mad5); - ASSERT_EQ(MemoryAllocatorDump::Flags::DEFAULT, shared_mad1->flags()); -} - -TEST(ProcessMemoryDumpTest, BackgroundModeTest) { - MemoryDumpArgs background_args = {MemoryDumpLevelOfDetail::BACKGROUND}; - std::unique_ptr pmd( - new ProcessMemoryDump(nullptr, background_args)); - ProcessMemoryDump::is_black_hole_non_fatal_for_testing_ = true; - SetAllocatorDumpNameWhitelistForTesting(kTestDumpNameWhitelist); - MemoryAllocatorDump* black_hole_mad = pmd->GetBlackHoleMad(); - - // Invalid dump names. - EXPECT_EQ(black_hole_mad, - pmd->CreateAllocatorDump("NotWhitelisted/TestName")); - EXPECT_EQ(black_hole_mad, pmd->CreateAllocatorDump("TestName")); - EXPECT_EQ(black_hole_mad, pmd->CreateAllocatorDump("Whitelisted/Test")); - EXPECT_EQ(black_hole_mad, - pmd->CreateAllocatorDump("Not/Whitelisted/TestName")); - EXPECT_EQ(black_hole_mad, - pmd->CreateAllocatorDump("Whitelisted/TestName/Google")); - EXPECT_EQ(black_hole_mad, - pmd->CreateAllocatorDump("Whitelisted/TestName/0x1a2Google")); - EXPECT_EQ(black_hole_mad, - pmd->CreateAllocatorDump("Whitelisted/TestName/__12/Google")); - - // Global dumps. - MemoryAllocatorDumpGuid guid(1); - EXPECT_EQ(black_hole_mad, pmd->CreateSharedGlobalAllocatorDump(guid)); - EXPECT_EQ(black_hole_mad, pmd->CreateWeakSharedGlobalAllocatorDump(guid)); - EXPECT_EQ(black_hole_mad, pmd->GetSharedGlobalAllocatorDump(guid)); - - // Suballocations. - pmd->AddSuballocation(guid, "malloc/allocated_objects"); - EXPECT_EQ(0u, pmd->allocator_dumps_edges_.size()); - EXPECT_EQ(0u, pmd->allocator_dumps_.size()); - - // Valid dump names. - EXPECT_NE(black_hole_mad, pmd->CreateAllocatorDump("Whitelisted/TestName")); - EXPECT_NE(black_hole_mad, - pmd->CreateAllocatorDump("Whitelisted/TestName_0xA1b2")); - EXPECT_NE(black_hole_mad, - pmd->CreateAllocatorDump("Whitelisted/0xaB/TestName")); - - // GetAllocatorDump is consistent. - EXPECT_EQ(black_hole_mad, pmd->GetAllocatorDump("NotWhitelisted/TestName")); - EXPECT_NE(black_hole_mad, pmd->GetAllocatorDump("Whitelisted/TestName")); -} - -#if defined(COUNT_RESIDENT_BYTES_SUPPORTED) -TEST(ProcessMemoryDumpTest, CountResidentBytes) { - const size_t page_size = ProcessMemoryDump::GetSystemPageSize(); - - // Allocate few page of dirty memory and check if it is resident. - const size_t size1 = 5 * page_size; - std::unique_ptr memory1( - static_cast(base::AlignedAlloc(size1, page_size))); - memset(memory1.get(), 0, size1); - size_t res1 = ProcessMemoryDump::CountResidentBytes(memory1.get(), size1); - ASSERT_EQ(res1, size1); - - // Allocate a large memory segment (> 8Mib). - const size_t kVeryLargeMemorySize = 15 * 1024 * 1024; - std::unique_ptr memory2( - static_cast(base::AlignedAlloc(kVeryLargeMemorySize, page_size))); - memset(memory2.get(), 0, kVeryLargeMemorySize); - size_t res2 = ProcessMemoryDump::CountResidentBytes(memory2.get(), - kVeryLargeMemorySize); - ASSERT_EQ(res2, kVeryLargeMemorySize); -} -#endif // defined(COUNT_RESIDENT_BYTES_SUPPORTED) - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/process_memory_maps.cc b/base/trace_event/process_memory_maps.cc deleted file mode 100644 index a121239604074ebd2d4bfb4897f7b41fc46df03a..0000000000000000000000000000000000000000 --- a/base/trace_event/process_memory_maps.cc +++ /dev/null @@ -1,77 +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. - -#include "base/trace_event/process_memory_maps.h" - -#include "base/format_macros.h" -#include "base/strings/stringprintf.h" -#include "base/trace_event/trace_event_argument.h" - -namespace base { -namespace trace_event { - -// static -const uint32_t ProcessMemoryMaps::VMRegion::kProtectionFlagsRead = 4; -const uint32_t ProcessMemoryMaps::VMRegion::kProtectionFlagsWrite = 2; -const uint32_t ProcessMemoryMaps::VMRegion::kProtectionFlagsExec = 1; -const uint32_t ProcessMemoryMaps::VMRegion::kProtectionFlagsMayshare = 128; - -ProcessMemoryMaps::VMRegion::VMRegion() - : start_address(0), - size_in_bytes(0), - protection_flags(0), - byte_stats_private_dirty_resident(0), - byte_stats_private_clean_resident(0), - byte_stats_shared_dirty_resident(0), - byte_stats_shared_clean_resident(0), - byte_stats_swapped(0), - byte_stats_proportional_resident(0) { -} - -ProcessMemoryMaps::VMRegion::VMRegion(const VMRegion& other) = default; - -ProcessMemoryMaps::ProcessMemoryMaps() { -} - -ProcessMemoryMaps::~ProcessMemoryMaps() { -} - -void ProcessMemoryMaps::AsValueInto(TracedValue* value) const { - static const char kHexFmt[] = "%" PRIx64; - - // Refer to the design doc goo.gl/sxfFY8 for the semantic of these fields. - value->BeginArray("vm_regions"); - for (const auto& region : vm_regions_) { - value->BeginDictionary(); - - value->SetString("sa", StringPrintf(kHexFmt, region.start_address)); - value->SetString("sz", StringPrintf(kHexFmt, region.size_in_bytes)); - value->SetInteger("pf", region.protection_flags); - value->SetString("mf", region.mapped_file); - - value->BeginDictionary("bs"); // byte stats - value->SetString( - "pss", StringPrintf(kHexFmt, region.byte_stats_proportional_resident)); - value->SetString( - "pd", StringPrintf(kHexFmt, region.byte_stats_private_dirty_resident)); - value->SetString( - "pc", StringPrintf(kHexFmt, region.byte_stats_private_clean_resident)); - value->SetString( - "sd", StringPrintf(kHexFmt, region.byte_stats_shared_dirty_resident)); - value->SetString( - "sc", StringPrintf(kHexFmt, region.byte_stats_shared_clean_resident)); - value->SetString("sw", StringPrintf(kHexFmt, region.byte_stats_swapped)); - value->EndDictionary(); - - value->EndDictionary(); - } - value->EndArray(); -} - -void ProcessMemoryMaps::Clear() { - vm_regions_.clear(); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/process_memory_maps.h b/base/trace_event/process_memory_maps.h deleted file mode 100644 index 6a7367437e55d0208ade37130b1ea08fcbf009d2..0000000000000000000000000000000000000000 --- a/base/trace_event/process_memory_maps.h +++ /dev/null @@ -1,72 +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. - -#ifndef BASE_TRACE_EVENT_PROCESS_MEMORY_MAPS_H_ -#define BASE_TRACE_EVENT_PROCESS_MEMORY_MAPS_H_ - -#include - -#include -#include - -#include "base/base_export.h" -#include "base/macros.h" - -namespace base { -namespace trace_event { - -class TracedValue; - -// Data model for process-wide memory stats. -class BASE_EXPORT ProcessMemoryMaps { - public: - struct BASE_EXPORT VMRegion { - static const uint32_t kProtectionFlagsRead; - static const uint32_t kProtectionFlagsWrite; - static const uint32_t kProtectionFlagsExec; - static const uint32_t kProtectionFlagsMayshare; - - VMRegion(); - VMRegion(const VMRegion& other); - - uint64_t start_address; - uint64_t size_in_bytes; - uint32_t protection_flags; - std::string mapped_file; - - // private_dirty_resident + private_clean_resident + shared_dirty_resident + - // shared_clean_resident = resident set size. - uint64_t byte_stats_private_dirty_resident; - uint64_t byte_stats_private_clean_resident; - uint64_t byte_stats_shared_dirty_resident; - uint64_t byte_stats_shared_clean_resident; - - uint64_t byte_stats_swapped; - - // For multiprocess accounting. - uint64_t byte_stats_proportional_resident; - }; - - ProcessMemoryMaps(); - ~ProcessMemoryMaps(); - - void AddVMRegion(const VMRegion& region) { vm_regions_.push_back(region); } - const std::vector& vm_regions() const { return vm_regions_; } - - // Called at trace generation time to populate the TracedValue. - void AsValueInto(TracedValue* value) const; - - // Clears up all the VMRegion(s) stored. - void Clear(); - - private: - std::vector vm_regions_; - - DISALLOW_COPY_AND_ASSIGN(ProcessMemoryMaps); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_PROCESS_MEMORY_MAPS_H_ diff --git a/base/trace_event/process_memory_totals.cc b/base/trace_event/process_memory_totals.cc deleted file mode 100644 index de27ab3d9d441a939f87ffa898e46d1bf8520a90..0000000000000000000000000000000000000000 --- a/base/trace_event/process_memory_totals.cc +++ /dev/null @@ -1,47 +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. - -#include "base/trace_event/process_memory_totals.h" - -#include "base/format_macros.h" -#include "base/strings/stringprintf.h" -#include "base/trace_event/trace_event_argument.h" - -namespace base { -namespace trace_event { - -ProcessMemoryTotals::ProcessMemoryTotals() - : resident_set_bytes_(0), - peak_resident_set_bytes_(0), - is_peak_rss_resetable_(false) { -} - -ProcessMemoryTotals::~ProcessMemoryTotals() {} - -void ProcessMemoryTotals::AsValueInto(TracedValue* value) const { - value->SetString("resident_set_bytes", - StringPrintf("%" PRIx64, resident_set_bytes_)); - if (peak_resident_set_bytes_ > 0) { - value->SetString("peak_resident_set_bytes", - StringPrintf("%" PRIx64, peak_resident_set_bytes_)); - value->SetBoolean("is_peak_rss_resetable", is_peak_rss_resetable_); - } - - for (const auto it : extra_fields_) { - value->SetString(it.first, StringPrintf("%" PRIx64, it.second)); - } -} - -void ProcessMemoryTotals::Clear() { - resident_set_bytes_ = 0; -} - -void ProcessMemoryTotals::SetExtraFieldInBytes(const char* name, - uint64_t value) { - DCHECK_EQ(0u, extra_fields_.count(name)); - extra_fields_[name] = value; -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/process_memory_totals.h b/base/trace_event/process_memory_totals.h deleted file mode 100644 index 329967a6ee74cbee73bfded84c20f3bfc6cb549f..0000000000000000000000000000000000000000 --- a/base/trace_event/process_memory_totals.h +++ /dev/null @@ -1,63 +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. - -#ifndef BASE_TRACE_EVENT_PROCESS_MEMORY_TOTALS_H_ -#define BASE_TRACE_EVENT_PROCESS_MEMORY_TOTALS_H_ - -#include - -#include - -#include "base/base_export.h" -#include "base/macros.h" - -namespace base { -namespace trace_event { - -class TracedValue; - -// Data model for process-wide memory stats. -class BASE_EXPORT ProcessMemoryTotals { - public: - ProcessMemoryTotals(); - ~ProcessMemoryTotals(); - - // Called at trace generation time to populate the TracedValue. - void AsValueInto(TracedValue* value) const; - - // Clears up all the data collected. - void Clear(); - - uint64_t resident_set_bytes() const { return resident_set_bytes_; } - void set_resident_set_bytes(uint64_t value) { resident_set_bytes_ = value; } - - uint64_t peak_resident_set_bytes() const { return peak_resident_set_bytes_; } - void set_peak_resident_set_bytes(uint64_t value) { - peak_resident_set_bytes_ = value; - } - - // On some platforms (recent linux kernels, see goo.gl/sMvAVz) the peak rss - // can be reset. When is_peak_rss_resettable == true, the peak refers to - // peak from the previous measurement. When false, it is the absolute peak - // since the start of the process. - bool is_peak_rss_resetable() const { return is_peak_rss_resetable_; } - void set_is_peak_rss_resetable(bool value) { is_peak_rss_resetable_ = value; } - - void SetExtraFieldInBytes(const char* name, uint64_t value); - - private: - uint64_t resident_set_bytes_; - uint64_t peak_resident_set_bytes_; - bool is_peak_rss_resetable_; - - // Extra metrics for OS-specific statistics. - std::map extra_fields_; - - DISALLOW_COPY_AND_ASSIGN(ProcessMemoryTotals); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_PROCESS_MEMORY_TOTALS_H_ diff --git a/base/trace_event/trace_buffer.cc b/base/trace_event/trace_buffer.cc deleted file mode 100644 index e26e9fd28fa806cfedcc00093dd5b336f8486dc0..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_buffer.cc +++ /dev/null @@ -1,345 +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. - -#include "base/trace_event/trace_buffer.h" - -#include -#include -#include - -#include "base/macros.h" -#include "base/trace_event/heap_profiler.h" -#include "base/trace_event/trace_event_impl.h" - -namespace base { -namespace trace_event { - -namespace { - -class TraceBufferRingBuffer : public TraceBuffer { - public: - TraceBufferRingBuffer(size_t max_chunks) - : max_chunks_(max_chunks), - recyclable_chunks_queue_(new size_t[queue_capacity()]), - queue_head_(0), - queue_tail_(max_chunks), - current_iteration_index_(0), - current_chunk_seq_(1) { - chunks_.reserve(max_chunks); - for (size_t i = 0; i < max_chunks; ++i) - recyclable_chunks_queue_[i] = i; - } - - std::unique_ptr GetChunk(size_t* index) override { - HEAP_PROFILER_SCOPED_IGNORE; - - // Because the number of threads is much less than the number of chunks, - // the queue should never be empty. - DCHECK(!QueueIsEmpty()); - - *index = recyclable_chunks_queue_[queue_head_]; - queue_head_ = NextQueueIndex(queue_head_); - current_iteration_index_ = queue_head_; - - if (*index >= chunks_.size()) - chunks_.resize(*index + 1); - - TraceBufferChunk* chunk = chunks_[*index].release(); - chunks_[*index] = NULL; // Put NULL in the slot of a in-flight chunk. - if (chunk) - chunk->Reset(current_chunk_seq_++); - else - chunk = new TraceBufferChunk(current_chunk_seq_++); - - return std::unique_ptr(chunk); - } - - void ReturnChunk(size_t index, - std::unique_ptr chunk) override { - // When this method is called, the queue should not be full because it - // can contain all chunks including the one to be returned. - DCHECK(!QueueIsFull()); - DCHECK(chunk); - DCHECK_LT(index, chunks_.size()); - DCHECK(!chunks_[index]); - chunks_[index] = std::move(chunk); - recyclable_chunks_queue_[queue_tail_] = index; - queue_tail_ = NextQueueIndex(queue_tail_); - } - - bool IsFull() const override { return false; } - - size_t Size() const override { - // This is approximate because not all of the chunks are full. - return chunks_.size() * TraceBufferChunk::kTraceBufferChunkSize; - } - - size_t Capacity() const override { - return max_chunks_ * TraceBufferChunk::kTraceBufferChunkSize; - } - - TraceEvent* GetEventByHandle(TraceEventHandle handle) override { - if (handle.chunk_index >= chunks_.size()) - return NULL; - TraceBufferChunk* chunk = chunks_[handle.chunk_index].get(); - if (!chunk || chunk->seq() != handle.chunk_seq) - return NULL; - return chunk->GetEventAt(handle.event_index); - } - - const TraceBufferChunk* NextChunk() override { - if (chunks_.empty()) - return NULL; - - while (current_iteration_index_ != queue_tail_) { - size_t chunk_index = recyclable_chunks_queue_[current_iteration_index_]; - current_iteration_index_ = NextQueueIndex(current_iteration_index_); - if (chunk_index >= chunks_.size()) // Skip uninitialized chunks. - continue; - DCHECK(chunks_[chunk_index]); - return chunks_[chunk_index].get(); - } - return NULL; - } - - void EstimateTraceMemoryOverhead( - TraceEventMemoryOverhead* overhead) override { - overhead->Add("TraceBufferRingBuffer", sizeof(*this)); - for (size_t queue_index = queue_head_; queue_index != queue_tail_; - queue_index = NextQueueIndex(queue_index)) { - size_t chunk_index = recyclable_chunks_queue_[queue_index]; - if (chunk_index >= chunks_.size()) // Skip uninitialized chunks. - continue; - chunks_[chunk_index]->EstimateTraceMemoryOverhead(overhead); - } - } - - private: - bool QueueIsEmpty() const { return queue_head_ == queue_tail_; } - - size_t QueueSize() const { - return queue_tail_ > queue_head_ - ? queue_tail_ - queue_head_ - : queue_tail_ + queue_capacity() - queue_head_; - } - - bool QueueIsFull() const { return QueueSize() == queue_capacity() - 1; } - - size_t queue_capacity() const { - // One extra space to help distinguish full state and empty state. - return max_chunks_ + 1; - } - - size_t NextQueueIndex(size_t index) const { - index++; - if (index >= queue_capacity()) - index = 0; - return index; - } - - size_t max_chunks_; - std::vector> chunks_; - - std::unique_ptr recyclable_chunks_queue_; - size_t queue_head_; - size_t queue_tail_; - - size_t current_iteration_index_; - uint32_t current_chunk_seq_; - - DISALLOW_COPY_AND_ASSIGN(TraceBufferRingBuffer); -}; - -class TraceBufferVector : public TraceBuffer { - public: - TraceBufferVector(size_t max_chunks) - : in_flight_chunk_count_(0), - current_iteration_index_(0), - max_chunks_(max_chunks) { - chunks_.reserve(max_chunks_); - } - - std::unique_ptr GetChunk(size_t* index) override { - HEAP_PROFILER_SCOPED_IGNORE; - - // This function may be called when adding normal events or indirectly from - // AddMetadataEventsWhileLocked(). We can not DECHECK(!IsFull()) because we - // have to add the metadata events and flush thread-local buffers even if - // the buffer is full. - *index = chunks_.size(); - // Put nullptr in the slot of a in-flight chunk. - chunks_.push_back(nullptr); - ++in_flight_chunk_count_; - // + 1 because zero chunk_seq is not allowed. - return std::unique_ptr( - new TraceBufferChunk(static_cast(*index) + 1)); - } - - void ReturnChunk(size_t index, - std::unique_ptr chunk) override { - DCHECK_GT(in_flight_chunk_count_, 0u); - DCHECK_LT(index, chunks_.size()); - DCHECK(!chunks_[index]); - --in_flight_chunk_count_; - chunks_[index] = std::move(chunk); - } - - bool IsFull() const override { return chunks_.size() >= max_chunks_; } - - size_t Size() const override { - // This is approximate because not all of the chunks are full. - return chunks_.size() * TraceBufferChunk::kTraceBufferChunkSize; - } - - size_t Capacity() const override { - return max_chunks_ * TraceBufferChunk::kTraceBufferChunkSize; - } - - TraceEvent* GetEventByHandle(TraceEventHandle handle) override { - if (handle.chunk_index >= chunks_.size()) - return NULL; - TraceBufferChunk* chunk = chunks_[handle.chunk_index].get(); - if (!chunk || chunk->seq() != handle.chunk_seq) - return NULL; - return chunk->GetEventAt(handle.event_index); - } - - const TraceBufferChunk* NextChunk() override { - while (current_iteration_index_ < chunks_.size()) { - // Skip in-flight chunks. - const TraceBufferChunk* chunk = chunks_[current_iteration_index_++].get(); - if (chunk) - return chunk; - } - return NULL; - } - - void EstimateTraceMemoryOverhead( - TraceEventMemoryOverhead* overhead) override { - const size_t chunks_ptr_vector_allocated_size = - sizeof(*this) + max_chunks_ * sizeof(decltype(chunks_)::value_type); - const size_t chunks_ptr_vector_resident_size = - sizeof(*this) + chunks_.size() * sizeof(decltype(chunks_)::value_type); - overhead->Add("TraceBufferVector", chunks_ptr_vector_allocated_size, - chunks_ptr_vector_resident_size); - for (size_t i = 0; i < chunks_.size(); ++i) { - TraceBufferChunk* chunk = chunks_[i].get(); - // Skip the in-flight (nullptr) chunks. They will be accounted by the - // per-thread-local dumpers, see ThreadLocalEventBuffer::OnMemoryDump. - if (chunk) - chunk->EstimateTraceMemoryOverhead(overhead); - } - } - - private: - size_t in_flight_chunk_count_; - size_t current_iteration_index_; - size_t max_chunks_; - std::vector> chunks_; - - DISALLOW_COPY_AND_ASSIGN(TraceBufferVector); -}; - -} // namespace - -TraceBufferChunk::TraceBufferChunk(uint32_t seq) : next_free_(0), seq_(seq) {} - -TraceBufferChunk::~TraceBufferChunk() {} - -void TraceBufferChunk::Reset(uint32_t new_seq) { - for (size_t i = 0; i < next_free_; ++i) - chunk_[i].Reset(); - next_free_ = 0; - seq_ = new_seq; - cached_overhead_estimate_.reset(); -} - -TraceEvent* TraceBufferChunk::AddTraceEvent(size_t* event_index) { - DCHECK(!IsFull()); - *event_index = next_free_++; - return &chunk_[*event_index]; -} - -void TraceBufferChunk::EstimateTraceMemoryOverhead( - TraceEventMemoryOverhead* overhead) { - if (!cached_overhead_estimate_) { - cached_overhead_estimate_.reset(new TraceEventMemoryOverhead); - - // When estimating the size of TraceBufferChunk, exclude the array of trace - // events, as they are computed individually below. - cached_overhead_estimate_->Add("TraceBufferChunk", - sizeof(*this) - sizeof(chunk_)); - } - - const size_t num_cached_estimated_events = - cached_overhead_estimate_->GetCount("TraceEvent"); - DCHECK_LE(num_cached_estimated_events, size()); - - if (IsFull() && num_cached_estimated_events == size()) { - overhead->Update(*cached_overhead_estimate_); - return; - } - - for (size_t i = num_cached_estimated_events; i < size(); ++i) - chunk_[i].EstimateTraceMemoryOverhead(cached_overhead_estimate_.get()); - - if (IsFull()) { - cached_overhead_estimate_->AddSelf(); - } else { - // The unused TraceEvents in |chunks_| are not cached. They will keep - // changing as new TraceEvents are added to this chunk, so they are - // computed on the fly. - const size_t num_unused_trace_events = capacity() - size(); - overhead->Add("TraceEvent (unused)", - num_unused_trace_events * sizeof(TraceEvent)); - } - - overhead->Update(*cached_overhead_estimate_); -} - -TraceResultBuffer::OutputCallback -TraceResultBuffer::SimpleOutput::GetCallback() { - return Bind(&SimpleOutput::Append, Unretained(this)); -} - -void TraceResultBuffer::SimpleOutput::Append( - const std::string& json_trace_output) { - json_output += json_trace_output; -} - -TraceResultBuffer::TraceResultBuffer() : append_comma_(false) {} - -TraceResultBuffer::~TraceResultBuffer() {} - -void TraceResultBuffer::SetOutputCallback( - const OutputCallback& json_chunk_callback) { - output_callback_ = json_chunk_callback; -} - -void TraceResultBuffer::Start() { - append_comma_ = false; - output_callback_.Run("["); -} - -void TraceResultBuffer::AddFragment(const std::string& trace_fragment) { - if (append_comma_) - output_callback_.Run(","); - append_comma_ = true; - output_callback_.Run(trace_fragment); -} - -void TraceResultBuffer::Finish() { - output_callback_.Run("]"); -} - -TraceBuffer* TraceBuffer::CreateTraceBufferRingBuffer(size_t max_chunks) { - return new TraceBufferRingBuffer(max_chunks); -} - -TraceBuffer* TraceBuffer::CreateTraceBufferVectorOfSize(size_t max_chunks) { - return new TraceBufferVector(max_chunks); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/trace_buffer.h b/base/trace_event/trace_buffer.h deleted file mode 100644 index 4885a3c7c0931b829cda6207808c378c09463e8e..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_buffer.h +++ /dev/null @@ -1,130 +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. - -#ifndef BASE_TRACE_EVENT_TRACE_BUFFER_H_ -#define BASE_TRACE_EVENT_TRACE_BUFFER_H_ - -#include -#include - -#include "base/base_export.h" -#include "base/trace_event/trace_event.h" -#include "base/trace_event/trace_event_impl.h" - -namespace base { - -namespace trace_event { - -// TraceBufferChunk is the basic unit of TraceBuffer. -class BASE_EXPORT TraceBufferChunk { - public: - explicit TraceBufferChunk(uint32_t seq); - ~TraceBufferChunk(); - - void Reset(uint32_t new_seq); - TraceEvent* AddTraceEvent(size_t* event_index); - bool IsFull() const { return next_free_ == kTraceBufferChunkSize; } - - uint32_t seq() const { return seq_; } - size_t capacity() const { return kTraceBufferChunkSize; } - size_t size() const { return next_free_; } - - TraceEvent* GetEventAt(size_t index) { - DCHECK(index < size()); - return &chunk_[index]; - } - const TraceEvent* GetEventAt(size_t index) const { - DCHECK(index < size()); - return &chunk_[index]; - } - - void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead); - - // These values must be kept consistent with the numbers of bits of - // chunk_index and event_index fields in TraceEventHandle - // (in trace_event_impl.h). - static const size_t kMaxChunkIndex = (1u << 26) - 1; - static const size_t kTraceBufferChunkSize = 64; - - private: - size_t next_free_; - std::unique_ptr cached_overhead_estimate_; - TraceEvent chunk_[kTraceBufferChunkSize]; - uint32_t seq_; -}; - -// TraceBuffer holds the events as they are collected. -class BASE_EXPORT TraceBuffer { - public: - virtual ~TraceBuffer() {} - - virtual std::unique_ptr GetChunk(size_t* index) = 0; - virtual void ReturnChunk(size_t index, - std::unique_ptr chunk) = 0; - - virtual bool IsFull() const = 0; - virtual size_t Size() const = 0; - virtual size_t Capacity() const = 0; - virtual TraceEvent* GetEventByHandle(TraceEventHandle handle) = 0; - - // For iteration. Each TraceBuffer can only be iterated once. - virtual const TraceBufferChunk* NextChunk() = 0; - - - // Computes an estimate of the size of the buffer, including all the retained - // objects. - virtual void EstimateTraceMemoryOverhead( - TraceEventMemoryOverhead* overhead) = 0; - - static TraceBuffer* CreateTraceBufferRingBuffer(size_t max_chunks); - static TraceBuffer* CreateTraceBufferVectorOfSize(size_t max_chunks); -}; - -// TraceResultBuffer collects and converts trace fragments returned by TraceLog -// to JSON output. -class BASE_EXPORT TraceResultBuffer { - public: - typedef base::Callback OutputCallback; - - // If you don't need to stream JSON chunks out efficiently, and just want to - // get a complete JSON string after calling Finish, use this struct to collect - // JSON trace output. - struct BASE_EXPORT SimpleOutput { - OutputCallback GetCallback(); - void Append(const std::string& json_string); - - // Do what you want with the json_output_ string after calling - // TraceResultBuffer::Finish. - std::string json_output; - }; - - TraceResultBuffer(); - ~TraceResultBuffer(); - - // Set callback. The callback will be called during Start with the initial - // JSON output and during AddFragment and Finish with following JSON output - // chunks. The callback target must live past the last calls to - // TraceResultBuffer::Start/AddFragment/Finish. - void SetOutputCallback(const OutputCallback& json_chunk_callback); - - // Start JSON output. This resets all internal state, so you can reuse - // the TraceResultBuffer by calling Start. - void Start(); - - // Call AddFragment 0 or more times to add trace fragments from TraceLog. - void AddFragment(const std::string& trace_fragment); - - // When all fragments have been added, call Finish to complete the JSON - // formatted output. - void Finish(); - - private: - OutputCallback output_callback_; - bool append_comma_; -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_BUFFER_H_ diff --git a/base/trace_event/trace_category.h b/base/trace_event/trace_category.h deleted file mode 100644 index 5a7915ac03866af8881307ea06985530fc0aaad4..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_category.h +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2016 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 BASE_TRACE_EVENT_TRACE_CATEGORY_H_ -#define BASE_TRACE_EVENT_TRACE_CATEGORY_H_ - -#include - -namespace base { -namespace trace_event { - -// Captures the state of an invidivual trace category. Nothing except tracing -// internals (e.g., TraceLog) is supposed to have non-const Category pointers. -struct TraceCategory { - // The TRACE_EVENT macros should only use this value as a bool. - // These enum values are effectively a public API and third_party projects - // depend on their value. Hence, never remove or recycle existing bits, unless - // you are sure that all the third-party projects that depend on this have - // been updated. - enum StateFlags : uint8_t { - ENABLED_FOR_RECORDING = 1 << 0, - - // Not used anymore. - DEPRECATED_ENABLED_FOR_MONITORING = 1 << 1, - DEPRECATED_ENABLED_FOR_EVENT_CALLBACK = 1 << 2, - - ENABLED_FOR_ETW_EXPORT = 1 << 3, - ENABLED_FOR_FILTERING = 1 << 4 - }; - - static const TraceCategory* FromStatePtr(const uint8_t* state_ptr) { - static_assert( - offsetof(TraceCategory, state_) == 0, - "|state_| must be the first field of the TraceCategory class."); - return reinterpret_cast(state_ptr); - } - - bool is_valid() const { return name_ != nullptr; } - void set_name(const char* name) { name_ = name; } - const char* name() const { - DCHECK(is_valid()); - return name_; - } - - // TODO(primiano): This is an intermediate solution to deal with the fact that - // today TRACE_EVENT* macros cache the state ptr. They should just cache the - // full TraceCategory ptr, which is immutable, and use these helper function - // here. This will get rid of the need of this awkward ptr getter completely. - const uint8_t* state_ptr() const { - return const_cast(&state_); - } - - uint8_t state() const { - return *const_cast(&state_); - } - - bool is_enabled() const { return state() != 0; } - - void set_state(uint8_t state) { - *const_cast(&state_) = state; - } - - void clear_state_flag(StateFlags flag) { set_state(state() & (~flag)); } - void set_state_flag(StateFlags flag) { set_state(state() | flag); } - - uint32_t enabled_filters() const { - return *const_cast(&enabled_filters_); - } - - bool is_filter_enabled(size_t index) const { - DCHECK(index < sizeof(enabled_filters_) * 8); - return (enabled_filters() & (1 << index)) != 0; - } - - void set_enabled_filters(uint32_t enabled_filters) { - *const_cast(&enabled_filters_) = enabled_filters; - } - - void reset_for_testing() { - set_state(0); - set_enabled_filters(0); - } - - // These fields should not be accessed directly, not even by tracing code. - // The only reason why these are not private is because it makes it impossible - // to have a global array of TraceCategory in category_registry.cc without - // creating initializers. See discussion on goo.gl/qhZN94 and - // crbug.com/{660967,660828}. - - // The enabled state. TRACE_EVENT* macros will capture events if any of the - // flags here are set. Since TRACE_EVENTx macros are used in a lot of - // fast-paths, accesses to this field are non-barriered and racy by design. - // This field is mutated when starting/stopping tracing and we don't care - // about missing some events. - uint8_t state_; - - // When ENABLED_FOR_FILTERING is set, this contains a bitmap to the - // coressponding filter (see event_filters.h). - uint32_t enabled_filters_; - - // TraceCategory group names are long lived static strings. - const char* name_; -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_CATEGORY_H_ diff --git a/base/trace_event/trace_config.cc b/base/trace_event/trace_config.cc deleted file mode 100644 index 7ee9a4a101b4f13dcd4455be4a5406aa1b9e5e45..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_config.cc +++ /dev/null @@ -1,592 +0,0 @@ -// Copyright (c) 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. - -#include "base/trace_event/trace_config.h" - -#include - -#include - -#include "base/json/json_reader.h" -#include "base/json/json_writer.h" -#include "base/memory/ptr_util.h" -#include "base/strings/string_split.h" -#include "base/trace_event/memory_dump_manager.h" -#include "base/trace_event/memory_dump_request_args.h" -#include "base/trace_event/trace_event.h" - -namespace base { -namespace trace_event { - -namespace { - -// String options that can be used to initialize TraceOptions. -const char kRecordUntilFull[] = "record-until-full"; -const char kRecordContinuously[] = "record-continuously"; -const char kRecordAsMuchAsPossible[] = "record-as-much-as-possible"; -const char kTraceToConsole[] = "trace-to-console"; -const char kEnableSystrace[] = "enable-systrace"; -const char kEnableArgumentFilter[] = "enable-argument-filter"; - -// String parameters that can be used to parse the trace config string. -const char kRecordModeParam[] = "record_mode"; -const char kEnableSystraceParam[] = "enable_systrace"; -const char kEnableArgumentFilterParam[] = "enable_argument_filter"; - -// String parameters that is used to parse memory dump config in trace config -// string. -const char kMemoryDumpConfigParam[] = "memory_dump_config"; -const char kAllowedDumpModesParam[] = "allowed_dump_modes"; -const char kTriggersParam[] = "triggers"; -const char kTriggerModeParam[] = "mode"; -const char kMinTimeBetweenDumps[] = "min_time_between_dumps_ms"; -const char kTriggerTypeParam[] = "type"; -const char kPeriodicIntervalLegacyParam[] = "periodic_interval_ms"; -const char kHeapProfilerOptions[] = "heap_profiler_options"; -const char kBreakdownThresholdBytes[] = "breakdown_threshold_bytes"; - -// String parameters used to parse category event filters. -const char kEventFiltersParam[] = "event_filters"; -const char kFilterPredicateParam[] = "filter_predicate"; -const char kFilterArgsParam[] = "filter_args"; - -// Default configuration of memory dumps. -const TraceConfig::MemoryDumpConfig::Trigger kDefaultHeavyMemoryDumpTrigger = { - 2000, // min_time_between_dumps_ms - MemoryDumpLevelOfDetail::DETAILED, MemoryDumpType::PERIODIC_INTERVAL}; -const TraceConfig::MemoryDumpConfig::Trigger kDefaultLightMemoryDumpTrigger = { - 250, // min_time_between_dumps_ms - MemoryDumpLevelOfDetail::LIGHT, MemoryDumpType::PERIODIC_INTERVAL}; - -class ConvertableTraceConfigToTraceFormat - : public base::trace_event::ConvertableToTraceFormat { - public: - explicit ConvertableTraceConfigToTraceFormat(const TraceConfig& trace_config) - : trace_config_(trace_config) {} - - ~ConvertableTraceConfigToTraceFormat() override {} - - void AppendAsTraceFormat(std::string* out) const override { - out->append(trace_config_.ToString()); - } - - private: - const TraceConfig trace_config_; -}; - -std::set GetDefaultAllowedMemoryDumpModes() { - std::set all_modes; - for (uint32_t mode = static_cast(MemoryDumpLevelOfDetail::FIRST); - mode <= static_cast(MemoryDumpLevelOfDetail::LAST); mode++) { - all_modes.insert(static_cast(mode)); - } - return all_modes; -} - -} // namespace - -TraceConfig::MemoryDumpConfig::HeapProfiler::HeapProfiler() - : breakdown_threshold_bytes(kDefaultBreakdownThresholdBytes) {} - -void TraceConfig::MemoryDumpConfig::HeapProfiler::Clear() { - breakdown_threshold_bytes = kDefaultBreakdownThresholdBytes; -} - -void TraceConfig::ResetMemoryDumpConfig( - const TraceConfig::MemoryDumpConfig& memory_dump_config) { - memory_dump_config_.Clear(); - memory_dump_config_ = memory_dump_config; -} - -TraceConfig::MemoryDumpConfig::MemoryDumpConfig() {} - -TraceConfig::MemoryDumpConfig::MemoryDumpConfig( - const MemoryDumpConfig& other) = default; - -TraceConfig::MemoryDumpConfig::~MemoryDumpConfig() {} - -void TraceConfig::MemoryDumpConfig::Clear() { - allowed_dump_modes.clear(); - triggers.clear(); - heap_profiler_options.Clear(); -} - -void TraceConfig::MemoryDumpConfig::Merge( - const TraceConfig::MemoryDumpConfig& config) { - triggers.insert(triggers.end(), config.triggers.begin(), - config.triggers.end()); - allowed_dump_modes.insert(config.allowed_dump_modes.begin(), - config.allowed_dump_modes.end()); - heap_profiler_options.breakdown_threshold_bytes = - std::min(heap_profiler_options.breakdown_threshold_bytes, - config.heap_profiler_options.breakdown_threshold_bytes); -} - -TraceConfig::EventFilterConfig::EventFilterConfig( - const std::string& predicate_name) - : predicate_name_(predicate_name) {} - -TraceConfig::EventFilterConfig::~EventFilterConfig() {} - -TraceConfig::EventFilterConfig::EventFilterConfig(const EventFilterConfig& tc) { - *this = tc; -} - -TraceConfig::EventFilterConfig& TraceConfig::EventFilterConfig::operator=( - const TraceConfig::EventFilterConfig& rhs) { - if (this == &rhs) - return *this; - - predicate_name_ = rhs.predicate_name_; - category_filter_ = rhs.category_filter_; - - if (rhs.args_) - args_ = rhs.args_->CreateDeepCopy(); - - return *this; -} - -void TraceConfig::EventFilterConfig::InitializeFromConfigDict( - const base::DictionaryValue* event_filter) { - category_filter_.InitializeFromConfigDict(*event_filter); - - const base::DictionaryValue* args_dict = nullptr; - if (event_filter->GetDictionary(kFilterArgsParam, &args_dict)) - args_ = args_dict->CreateDeepCopy(); -} - -void TraceConfig::EventFilterConfig::SetCategoryFilter( - const TraceConfigCategoryFilter& category_filter) { - category_filter_ = category_filter; -} - -void TraceConfig::EventFilterConfig::ToDict( - DictionaryValue* filter_dict) const { - filter_dict->SetString(kFilterPredicateParam, predicate_name()); - - category_filter_.ToDict(filter_dict); - - if (args_) - filter_dict->Set(kFilterArgsParam, args_->CreateDeepCopy()); -} - -bool TraceConfig::EventFilterConfig::GetArgAsSet( - const char* key, - std::unordered_set* out_set) const { - const ListValue* list = nullptr; - if (!args_->GetList(key, &list)) - return false; - for (size_t i = 0; i < list->GetSize(); ++i) { - std::string value; - if (list->GetString(i, &value)) - out_set->insert(value); - } - return true; -} - -bool TraceConfig::EventFilterConfig::IsCategoryGroupEnabled( - const StringPiece& category_group_name) const { - return category_filter_.IsCategoryGroupEnabled(category_group_name); -} - -TraceConfig::TraceConfig() { - InitializeDefault(); -} - -TraceConfig::TraceConfig(StringPiece category_filter_string, - StringPiece trace_options_string) { - InitializeFromStrings(category_filter_string, trace_options_string); -} - -TraceConfig::TraceConfig(StringPiece category_filter_string, - TraceRecordMode record_mode) { - std::string trace_options_string; - switch (record_mode) { - case RECORD_UNTIL_FULL: - trace_options_string = kRecordUntilFull; - break; - case RECORD_CONTINUOUSLY: - trace_options_string = kRecordContinuously; - break; - case RECORD_AS_MUCH_AS_POSSIBLE: - trace_options_string = kRecordAsMuchAsPossible; - break; - case ECHO_TO_CONSOLE: - trace_options_string = kTraceToConsole; - break; - default: - NOTREACHED(); - } - InitializeFromStrings(category_filter_string, trace_options_string); -} - -TraceConfig::TraceConfig(const DictionaryValue& config) { - InitializeFromConfigDict(config); -} - -TraceConfig::TraceConfig(StringPiece config_string) { - if (!config_string.empty()) - InitializeFromConfigString(config_string); - else - InitializeDefault(); -} - -TraceConfig::TraceConfig(const TraceConfig& tc) - : record_mode_(tc.record_mode_), - enable_systrace_(tc.enable_systrace_), - enable_argument_filter_(tc.enable_argument_filter_), - category_filter_(tc.category_filter_), - memory_dump_config_(tc.memory_dump_config_), - event_filters_(tc.event_filters_) {} - -TraceConfig::~TraceConfig() { -} - -TraceConfig& TraceConfig::operator=(const TraceConfig& rhs) { - if (this == &rhs) - return *this; - - record_mode_ = rhs.record_mode_; - enable_systrace_ = rhs.enable_systrace_; - enable_argument_filter_ = rhs.enable_argument_filter_; - category_filter_ = rhs.category_filter_; - memory_dump_config_ = rhs.memory_dump_config_; - event_filters_ = rhs.event_filters_; - return *this; -} - -const TraceConfig::StringList& TraceConfig::GetSyntheticDelayValues() const { - return category_filter_.synthetic_delays(); -} - -std::string TraceConfig::ToString() const { - std::unique_ptr dict = ToDict(); - std::string json; - JSONWriter::Write(*dict, &json); - return json; -} - -std::unique_ptr -TraceConfig::AsConvertableToTraceFormat() const { - return MakeUnique(*this); -} - -std::string TraceConfig::ToCategoryFilterString() const { - return category_filter_.ToFilterString(); -} - -bool TraceConfig::IsCategoryGroupEnabled( - const StringPiece& category_group_name) const { - // TraceLog should call this method only as part of enabling/disabling - // categories. - return category_filter_.IsCategoryGroupEnabled(category_group_name); -} - -void TraceConfig::Merge(const TraceConfig& config) { - if (record_mode_ != config.record_mode_ - || enable_systrace_ != config.enable_systrace_ - || enable_argument_filter_ != config.enable_argument_filter_) { - DLOG(ERROR) << "Attempting to merge trace config with a different " - << "set of options."; - } - - category_filter_.Merge(config.category_filter_); - - memory_dump_config_.Merge(config.memory_dump_config_); - - event_filters_.insert(event_filters_.end(), config.event_filters().begin(), - config.event_filters().end()); -} - -void TraceConfig::Clear() { - record_mode_ = RECORD_UNTIL_FULL; - enable_systrace_ = false; - enable_argument_filter_ = false; - category_filter_.Clear(); - memory_dump_config_.Clear(); - event_filters_.clear(); -} - -void TraceConfig::InitializeDefault() { - record_mode_ = RECORD_UNTIL_FULL; - enable_systrace_ = false; - enable_argument_filter_ = false; -} - -void TraceConfig::InitializeFromConfigDict(const DictionaryValue& dict) { - record_mode_ = RECORD_UNTIL_FULL; - std::string record_mode; - if (dict.GetString(kRecordModeParam, &record_mode)) { - if (record_mode == kRecordUntilFull) { - record_mode_ = RECORD_UNTIL_FULL; - } else if (record_mode == kRecordContinuously) { - record_mode_ = RECORD_CONTINUOUSLY; - } else if (record_mode == kTraceToConsole) { - record_mode_ = ECHO_TO_CONSOLE; - } else if (record_mode == kRecordAsMuchAsPossible) { - record_mode_ = RECORD_AS_MUCH_AS_POSSIBLE; - } - } - - bool val; - enable_systrace_ = dict.GetBoolean(kEnableSystraceParam, &val) ? val : false; - enable_argument_filter_ = - dict.GetBoolean(kEnableArgumentFilterParam, &val) ? val : false; - - category_filter_.InitializeFromConfigDict(dict); - - const base::ListValue* category_event_filters = nullptr; - if (dict.GetList(kEventFiltersParam, &category_event_filters)) - SetEventFiltersFromConfigList(*category_event_filters); - - if (category_filter_.IsCategoryEnabled(MemoryDumpManager::kTraceCategory)) { - // If dump triggers not set, the client is using the legacy with just - // category enabled. So, use the default periodic dump config. - const DictionaryValue* memory_dump_config = nullptr; - if (dict.GetDictionary(kMemoryDumpConfigParam, &memory_dump_config)) - SetMemoryDumpConfigFromConfigDict(*memory_dump_config); - else - SetDefaultMemoryDumpConfig(); - } -} - -void TraceConfig::InitializeFromConfigString(StringPiece config_string) { - auto dict = DictionaryValue::From(JSONReader::Read(config_string)); - if (dict) - InitializeFromConfigDict(*dict); - else - InitializeDefault(); -} - -void TraceConfig::InitializeFromStrings(StringPiece category_filter_string, - StringPiece trace_options_string) { - if (!category_filter_string.empty()) - category_filter_.InitializeFromString(category_filter_string); - - record_mode_ = RECORD_UNTIL_FULL; - enable_systrace_ = false; - enable_argument_filter_ = false; - if (!trace_options_string.empty()) { - std::vector split = - SplitString(trace_options_string, ",", TRIM_WHITESPACE, SPLIT_WANT_ALL); - for (const std::string& token : split) { - if (token == kRecordUntilFull) { - record_mode_ = RECORD_UNTIL_FULL; - } else if (token == kRecordContinuously) { - record_mode_ = RECORD_CONTINUOUSLY; - } else if (token == kTraceToConsole) { - record_mode_ = ECHO_TO_CONSOLE; - } else if (token == kRecordAsMuchAsPossible) { - record_mode_ = RECORD_AS_MUCH_AS_POSSIBLE; - } else if (token == kEnableSystrace) { - enable_systrace_ = true; - } else if (token == kEnableArgumentFilter) { - enable_argument_filter_ = true; - } - } - } - - if (category_filter_.IsCategoryEnabled(MemoryDumpManager::kTraceCategory)) { - SetDefaultMemoryDumpConfig(); - } -} - -void TraceConfig::SetMemoryDumpConfigFromConfigDict( - const DictionaryValue& memory_dump_config) { - // Set allowed dump modes. - memory_dump_config_.allowed_dump_modes.clear(); - const ListValue* allowed_modes_list; - if (memory_dump_config.GetList(kAllowedDumpModesParam, &allowed_modes_list)) { - for (size_t i = 0; i < allowed_modes_list->GetSize(); ++i) { - std::string level_of_detail_str; - allowed_modes_list->GetString(i, &level_of_detail_str); - memory_dump_config_.allowed_dump_modes.insert( - StringToMemoryDumpLevelOfDetail(level_of_detail_str)); - } - } else { - // If allowed modes param is not given then allow all modes by default. - memory_dump_config_.allowed_dump_modes = GetDefaultAllowedMemoryDumpModes(); - } - - // Set triggers - memory_dump_config_.triggers.clear(); - const ListValue* trigger_list = nullptr; - if (memory_dump_config.GetList(kTriggersParam, &trigger_list) && - trigger_list->GetSize() > 0) { - for (size_t i = 0; i < trigger_list->GetSize(); ++i) { - const DictionaryValue* trigger = nullptr; - if (!trigger_list->GetDictionary(i, &trigger)) - continue; - - MemoryDumpConfig::Trigger dump_config; - int interval = 0; - if (!trigger->GetInteger(kMinTimeBetweenDumps, &interval)) { - // If "min_time_between_dumps_ms" param was not given, then the trace - // config uses old format where only periodic dumps are supported. - trigger->GetInteger(kPeriodicIntervalLegacyParam, &interval); - dump_config.trigger_type = MemoryDumpType::PERIODIC_INTERVAL; - } else { - std::string trigger_type_str; - trigger->GetString(kTriggerTypeParam, &trigger_type_str); - dump_config.trigger_type = StringToMemoryDumpType(trigger_type_str); - } - DCHECK_GT(interval, 0); - dump_config.min_time_between_dumps_ms = static_cast(interval); - - std::string level_of_detail_str; - trigger->GetString(kTriggerModeParam, &level_of_detail_str); - dump_config.level_of_detail = - StringToMemoryDumpLevelOfDetail(level_of_detail_str); - - memory_dump_config_.triggers.push_back(dump_config); - } - } - - // Set heap profiler options - const DictionaryValue* heap_profiler_options = nullptr; - if (memory_dump_config.GetDictionary(kHeapProfilerOptions, - &heap_profiler_options)) { - int min_size_bytes = 0; - if (heap_profiler_options->GetInteger(kBreakdownThresholdBytes, - &min_size_bytes) - && min_size_bytes >= 0) { - memory_dump_config_.heap_profiler_options.breakdown_threshold_bytes = - static_cast(min_size_bytes); - } else { - memory_dump_config_.heap_profiler_options.breakdown_threshold_bytes = - MemoryDumpConfig::HeapProfiler::kDefaultBreakdownThresholdBytes; - } - } -} - -void TraceConfig::SetDefaultMemoryDumpConfig() { - memory_dump_config_.Clear(); - memory_dump_config_.triggers.push_back(kDefaultHeavyMemoryDumpTrigger); - memory_dump_config_.triggers.push_back(kDefaultLightMemoryDumpTrigger); - memory_dump_config_.allowed_dump_modes = GetDefaultAllowedMemoryDumpModes(); -} - -void TraceConfig::SetEventFiltersFromConfigList( - const base::ListValue& category_event_filters) { - event_filters_.clear(); - - for (size_t event_filter_index = 0; - event_filter_index < category_event_filters.GetSize(); - ++event_filter_index) { - const base::DictionaryValue* event_filter = nullptr; - if (!category_event_filters.GetDictionary(event_filter_index, - &event_filter)) - continue; - - std::string predicate_name; - CHECK(event_filter->GetString(kFilterPredicateParam, &predicate_name)) - << "Invalid predicate name in category event filter."; - - EventFilterConfig new_config(predicate_name); - new_config.InitializeFromConfigDict(event_filter); - event_filters_.push_back(new_config); - } -} - -std::unique_ptr TraceConfig::ToDict() const { - auto dict = MakeUnique(); - switch (record_mode_) { - case RECORD_UNTIL_FULL: - dict->SetString(kRecordModeParam, kRecordUntilFull); - break; - case RECORD_CONTINUOUSLY: - dict->SetString(kRecordModeParam, kRecordContinuously); - break; - case RECORD_AS_MUCH_AS_POSSIBLE: - dict->SetString(kRecordModeParam, kRecordAsMuchAsPossible); - break; - case ECHO_TO_CONSOLE: - dict->SetString(kRecordModeParam, kTraceToConsole); - break; - default: - NOTREACHED(); - } - - dict->SetBoolean(kEnableSystraceParam, enable_systrace_); - dict->SetBoolean(kEnableArgumentFilterParam, enable_argument_filter_); - - category_filter_.ToDict(dict.get()); - - if (!event_filters_.empty()) { - std::unique_ptr filter_list(new base::ListValue()); - for (const EventFilterConfig& filter : event_filters_) { - std::unique_ptr filter_dict( - new base::DictionaryValue()); - filter.ToDict(filter_dict.get()); - filter_list->Append(std::move(filter_dict)); - } - dict->Set(kEventFiltersParam, std::move(filter_list)); - } - - if (category_filter_.IsCategoryEnabled(MemoryDumpManager::kTraceCategory)) { - auto allowed_modes = MakeUnique(); - for (auto dump_mode : memory_dump_config_.allowed_dump_modes) - allowed_modes->AppendString(MemoryDumpLevelOfDetailToString(dump_mode)); - - auto memory_dump_config = MakeUnique(); - memory_dump_config->Set(kAllowedDumpModesParam, std::move(allowed_modes)); - - auto triggers_list = MakeUnique(); - for (const auto& config : memory_dump_config_.triggers) { - auto trigger_dict = MakeUnique(); - trigger_dict->SetString(kTriggerTypeParam, - MemoryDumpTypeToString(config.trigger_type)); - trigger_dict->SetInteger( - kMinTimeBetweenDumps, - static_cast(config.min_time_between_dumps_ms)); - trigger_dict->SetString( - kTriggerModeParam, - MemoryDumpLevelOfDetailToString(config.level_of_detail)); - triggers_list->Append(std::move(trigger_dict)); - } - - // Empty triggers will still be specified explicitly since it means that - // the periodic dumps are not enabled. - memory_dump_config->Set(kTriggersParam, std::move(triggers_list)); - - if (memory_dump_config_.heap_profiler_options.breakdown_threshold_bytes != - MemoryDumpConfig::HeapProfiler::kDefaultBreakdownThresholdBytes) { - auto options = MakeUnique(); - options->SetInteger( - kBreakdownThresholdBytes, - memory_dump_config_.heap_profiler_options.breakdown_threshold_bytes); - memory_dump_config->Set(kHeapProfilerOptions, std::move(options)); - } - dict->Set(kMemoryDumpConfigParam, std::move(memory_dump_config)); - } - return dict; -} - -std::string TraceConfig::ToTraceOptionsString() const { - std::string ret; - switch (record_mode_) { - case RECORD_UNTIL_FULL: - ret = kRecordUntilFull; - break; - case RECORD_CONTINUOUSLY: - ret = kRecordContinuously; - break; - case RECORD_AS_MUCH_AS_POSSIBLE: - ret = kRecordAsMuchAsPossible; - break; - case ECHO_TO_CONSOLE: - ret = kTraceToConsole; - break; - default: - NOTREACHED(); - } - if (enable_systrace_) - ret = ret + "," + kEnableSystrace; - if (enable_argument_filter_) - ret = ret + "," + kEnableArgumentFilter; - return ret; -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/trace_config.h b/base/trace_event/trace_config.h deleted file mode 100644 index 13b2f5f0ee7d14a0024cc2f70fd84eea5d2b272e..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_config.h +++ /dev/null @@ -1,300 +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. - -#ifndef BASE_TRACE_EVENT_TRACE_CONFIG_H_ -#define BASE_TRACE_EVENT_TRACE_CONFIG_H_ - -#include - -#include -#include -#include -#include -#include - -#include "base/base_export.h" -#include "base/gtest_prod_util.h" -#include "base/strings/string_piece.h" -#include "base/trace_event/memory_dump_request_args.h" -#include "base/trace_event/trace_config_category_filter.h" -#include "base/values.h" - -namespace base { -namespace trace_event { - -class ConvertableToTraceFormat; - -// Options determines how the trace buffer stores data. -enum TraceRecordMode { - // Record until the trace buffer is full. - RECORD_UNTIL_FULL, - - // Record until the user ends the trace. The trace buffer is a fixed size - // and we use it as a ring buffer during recording. - RECORD_CONTINUOUSLY, - - // Record until the trace buffer is full, but with a huge buffer size. - RECORD_AS_MUCH_AS_POSSIBLE, - - // Echo to console. Events are discarded. - ECHO_TO_CONSOLE, -}; - -class BASE_EXPORT TraceConfig { - public: - using StringList = std::vector; - - // Specifies the memory dump config for tracing. - // Used only when "memory-infra" category is enabled. - struct BASE_EXPORT MemoryDumpConfig { - MemoryDumpConfig(); - MemoryDumpConfig(const MemoryDumpConfig& other); - ~MemoryDumpConfig(); - - // Specifies the triggers in the memory dump config. - struct Trigger { - uint32_t min_time_between_dumps_ms; - MemoryDumpLevelOfDetail level_of_detail; - MemoryDumpType trigger_type; - }; - - // Specifies the configuration options for the heap profiler. - struct HeapProfiler { - // Default value for |breakdown_threshold_bytes|. - enum { kDefaultBreakdownThresholdBytes = 1024 }; - - HeapProfiler(); - - // Reset the options to default. - void Clear(); - - uint32_t breakdown_threshold_bytes; - }; - - // Reset the values in the config. - void Clear(); - - void Merge(const MemoryDumpConfig& config); - - // Set of memory dump modes allowed for the tracing session. The explicitly - // triggered dumps will be successful only if the dump mode is allowed in - // the config. - std::set allowed_dump_modes; - - std::vector triggers; - HeapProfiler heap_profiler_options; - }; - - class BASE_EXPORT EventFilterConfig { - public: - EventFilterConfig(const std::string& predicate_name); - EventFilterConfig(const EventFilterConfig& tc); - - ~EventFilterConfig(); - - EventFilterConfig& operator=(const EventFilterConfig& rhs); - - void InitializeFromConfigDict(const base::DictionaryValue* event_filter); - - void SetCategoryFilter(const TraceConfigCategoryFilter& category_filter); - - void ToDict(DictionaryValue* filter_dict) const; - - bool GetArgAsSet(const char* key, std::unordered_set*) const; - - bool IsCategoryGroupEnabled(const StringPiece& category_group_name) const; - - const std::string& predicate_name() const { return predicate_name_; } - base::DictionaryValue* filter_args() const { return args_.get(); } - const TraceConfigCategoryFilter& category_filter() const { - return category_filter_; - } - - private: - std::string predicate_name_; - TraceConfigCategoryFilter category_filter_; - std::unique_ptr args_; - }; - typedef std::vector EventFilters; - - TraceConfig(); - - // Create TraceConfig object from category filter and trace options strings. - // - // |category_filter_string| is a comma-delimited list of category wildcards. - // A category can have an optional '-' prefix to make it an excluded category. - // All the same rules apply above, so for example, having both included and - // excluded categories in the same list would not be supported. - // - // Category filters can also be used to configure synthetic delays. - // - // |trace_options_string| is a comma-delimited list of trace options. - // Possible options are: "record-until-full", "record-continuously", - // "record-as-much-as-possible", "trace-to-console", "enable-systrace" and - // "enable-argument-filter". - // The first 4 options are trace recoding modes and hence - // mutually exclusive. If more than one trace recording modes appear in the - // options_string, the last one takes precedence. If none of the trace - // recording mode is specified, recording mode is RECORD_UNTIL_FULL. - // - // The trace option will first be reset to the default option - // (record_mode set to RECORD_UNTIL_FULL, enable_systrace and - // enable_argument_filter set to false) before options parsed from - // |trace_options_string| are applied on it. If |trace_options_string| is - // invalid, the final state of trace options is undefined. - // - // Example: TraceConfig("test_MyTest*", "record-until-full"); - // Example: TraceConfig("test_MyTest*,test_OtherStuff", - // "record-continuously"); - // Example: TraceConfig("-excluded_category1,-excluded_category2", - // "record-until-full, trace-to-console"); - // would set ECHO_TO_CONSOLE as the recording mode. - // Example: TraceConfig("-*,webkit", ""); - // would disable everything but webkit; and use default options. - // Example: TraceConfig("-webkit", ""); - // would enable everything but webkit; and use default options. - // Example: TraceConfig("DELAY(gpu.PresentingFrame;16)", ""); - // would make swap buffers always take at least 16 ms; and use - // default options. - // Example: TraceConfig("DELAY(gpu.PresentingFrame;16;oneshot)", ""); - // would make swap buffers take at least 16 ms the first time it is - // called; and use default options. - // Example: TraceConfig("DELAY(gpu.PresentingFrame;16;alternating)", ""); - // would make swap buffers take at least 16 ms every other time it - // is called; and use default options. - TraceConfig(StringPiece category_filter_string, - StringPiece trace_options_string); - - TraceConfig(StringPiece category_filter_string, TraceRecordMode record_mode); - - // Create TraceConfig object from the trace config string. - // - // |config_string| is a dictionary formatted as a JSON string, containing both - // category filters and trace options. - // - // Example: - // { - // "record_mode": "record-continuously", - // "enable_systrace": true, - // "enable_argument_filter": true, - // "included_categories": ["included", - // "inc_pattern*", - // "disabled-by-default-memory-infra"], - // "excluded_categories": ["excluded", "exc_pattern*"], - // "synthetic_delays": ["test.Delay1;16", "test.Delay2;32"], - // "memory_dump_config": { - // "triggers": [ - // { - // "mode": "detailed", - // "periodic_interval_ms": 2000 - // } - // ] - // } - // } - // - // Note: memory_dump_config can be specified only if - // disabled-by-default-memory-infra category is enabled. - explicit TraceConfig(StringPiece config_string); - - // Functionally identical to the above, but takes a parsed dictionary as input - // instead of its JSON serialization. - explicit TraceConfig(const DictionaryValue& config); - - TraceConfig(const TraceConfig& tc); - - ~TraceConfig(); - - TraceConfig& operator=(const TraceConfig& rhs); - - // Return a list of the synthetic delays specified in this category filter. - const StringList& GetSyntheticDelayValues() const; - - TraceRecordMode GetTraceRecordMode() const { return record_mode_; } - bool IsSystraceEnabled() const { return enable_systrace_; } - bool IsArgumentFilterEnabled() const { return enable_argument_filter_; } - - void SetTraceRecordMode(TraceRecordMode mode) { record_mode_ = mode; } - void EnableSystrace() { enable_systrace_ = true; } - void EnableArgumentFilter() { enable_argument_filter_ = true; } - - // Writes the string representation of the TraceConfig. The string is JSON - // formatted. - std::string ToString() const; - - // Returns a copy of the TraceConfig wrapped in a ConvertableToTraceFormat - std::unique_ptr AsConvertableToTraceFormat() const; - - // Write the string representation of the CategoryFilter part. - std::string ToCategoryFilterString() const; - - // Returns true if at least one category in the list is enabled by this - // trace config. This is used to determine if the category filters are - // enabled in the TRACE_* macros. - bool IsCategoryGroupEnabled(const StringPiece& category_group_name) const; - - // Merges config with the current TraceConfig - void Merge(const TraceConfig& config); - - void Clear(); - - // Clears and resets the memory dump config. - void ResetMemoryDumpConfig(const MemoryDumpConfig& memory_dump_config); - - const TraceConfigCategoryFilter& category_filter() const { - return category_filter_; - } - - const MemoryDumpConfig& memory_dump_config() const { - return memory_dump_config_; - } - - const EventFilters& event_filters() const { return event_filters_; } - void SetEventFilters(const EventFilters& filter_configs) { - event_filters_ = filter_configs; - } - - private: - FRIEND_TEST_ALL_PREFIXES(TraceConfigTest, TraceConfigFromValidLegacyFormat); - FRIEND_TEST_ALL_PREFIXES(TraceConfigTest, - TraceConfigFromInvalidLegacyStrings); - - // The default trace config, used when none is provided. - // Allows all non-disabled-by-default categories through, except if they end - // in the suffix 'Debug' or 'Test'. - void InitializeDefault(); - - // Initialize from a config dictionary. - void InitializeFromConfigDict(const DictionaryValue& dict); - - // Initialize from a config string. - void InitializeFromConfigString(StringPiece config_string); - - // Initialize from category filter and trace options strings - void InitializeFromStrings(StringPiece category_filter_string, - StringPiece trace_options_string); - - void SetMemoryDumpConfigFromConfigDict( - const DictionaryValue& memory_dump_config); - void SetDefaultMemoryDumpConfig(); - - void SetEventFiltersFromConfigList(const base::ListValue& event_filters); - std::unique_ptr ToDict() const; - - std::string ToTraceOptionsString() const; - - TraceRecordMode record_mode_; - bool enable_systrace_ : 1; - bool enable_argument_filter_ : 1; - - TraceConfigCategoryFilter category_filter_; - - MemoryDumpConfig memory_dump_config_; - - EventFilters event_filters_; -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_CONFIG_H_ diff --git a/base/trace_event/trace_config_category_filter.cc b/base/trace_event/trace_config_category_filter.cc deleted file mode 100644 index 234db18c5cddcc1ff47e262595523f9261ed7e66..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_config_category_filter.cc +++ /dev/null @@ -1,297 +0,0 @@ -// 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. - -#include "base/trace_event/trace_config_category_filter.h" - -#include "base/memory/ptr_util.h" -#include "base/strings/pattern.h" -#include "base/strings/string_split.h" -#include "base/strings/string_tokenizer.h" -#include "base/strings/string_util.h" -#include "base/strings/stringprintf.h" -#include "base/trace_event/trace_event.h" - -namespace base { -namespace trace_event { - -namespace { -const char kIncludedCategoriesParam[] = "included_categories"; -const char kExcludedCategoriesParam[] = "excluded_categories"; -const char kSyntheticDelaysParam[] = "synthetic_delays"; - -const char kSyntheticDelayCategoryFilterPrefix[] = "DELAY("; -} - -TraceConfigCategoryFilter::TraceConfigCategoryFilter() {} - -TraceConfigCategoryFilter::TraceConfigCategoryFilter( - const TraceConfigCategoryFilter& other) - : included_categories_(other.included_categories_), - disabled_categories_(other.disabled_categories_), - excluded_categories_(other.excluded_categories_), - synthetic_delays_(other.synthetic_delays_) {} - -TraceConfigCategoryFilter::~TraceConfigCategoryFilter() {} - -TraceConfigCategoryFilter& TraceConfigCategoryFilter::operator=( - const TraceConfigCategoryFilter& rhs) { - included_categories_ = rhs.included_categories_; - disabled_categories_ = rhs.disabled_categories_; - excluded_categories_ = rhs.excluded_categories_; - synthetic_delays_ = rhs.synthetic_delays_; - return *this; -} - -void TraceConfigCategoryFilter::InitializeFromString( - const StringPiece& category_filter_string) { - std::vector split = SplitStringPiece( - category_filter_string, ",", TRIM_WHITESPACE, SPLIT_WANT_ALL); - for (const StringPiece& category : split) { - // Ignore empty categories. - if (category.empty()) - continue; - // Synthetic delays are of the form 'DELAY(delay;option;option;...)'. - if (StartsWith(category, kSyntheticDelayCategoryFilterPrefix, - CompareCase::SENSITIVE) && - category.back() == ')') { - StringPiece synthetic_category = category.substr( - strlen(kSyntheticDelayCategoryFilterPrefix), - category.size() - strlen(kSyntheticDelayCategoryFilterPrefix) - 1); - size_t name_length = synthetic_category.find(';'); - if (name_length != std::string::npos && name_length > 0 && - name_length != synthetic_category.size() - 1) { - synthetic_delays_.push_back(synthetic_category.as_string()); - } - } else if (category.front() == '-') { - // Excluded categories start with '-'. - // Remove '-' from category string. - excluded_categories_.push_back(category.substr(1).as_string()); - } else if (category.starts_with(TRACE_DISABLED_BY_DEFAULT(""))) { - disabled_categories_.push_back(category.as_string()); - } else { - included_categories_.push_back(category.as_string()); - } - } -} - -void TraceConfigCategoryFilter::InitializeFromConfigDict( - const DictionaryValue& dict) { - const ListValue* category_list = nullptr; - if (dict.GetList(kIncludedCategoriesParam, &category_list)) - SetCategoriesFromIncludedList(*category_list); - if (dict.GetList(kExcludedCategoriesParam, &category_list)) - SetCategoriesFromExcludedList(*category_list); - if (dict.GetList(kSyntheticDelaysParam, &category_list)) - SetSyntheticDelaysFromList(*category_list); -} - -bool TraceConfigCategoryFilter::IsCategoryGroupEnabled( - const StringPiece& category_group_name) const { - bool had_enabled_by_default = false; - DCHECK(!category_group_name.empty()); - CStringTokenizer category_group_tokens(category_group_name.begin(), - category_group_name.end(), ","); - while (category_group_tokens.GetNext()) { - StringPiece category_group_token = category_group_tokens.token_piece(); - // Don't allow empty tokens, nor tokens with leading or trailing space. - DCHECK(IsCategoryNameAllowed(category_group_token)) - << "Disallowed category string"; - if (IsCategoryEnabled(category_group_token)) - return true; - - if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*"))) - had_enabled_by_default = true; - } - // Do a second pass to check for explicitly disabled categories - // (those explicitly enabled have priority due to first pass). - category_group_tokens.Reset(); - bool category_group_disabled = false; - while (category_group_tokens.GetNext()) { - StringPiece category_group_token = category_group_tokens.token_piece(); - for (const std::string& category : excluded_categories_) { - if (MatchPattern(category_group_token, category)) { - // Current token of category_group_name is present in excluded_list. - // Flag the exclusion and proceed further to check if any of the - // remaining categories of category_group_name is not present in the - // excluded_ list. - category_group_disabled = true; - break; - } - // One of the category of category_group_name is not present in - // excluded_ list. So, if it's not a disabled-by-default category, - // it has to be included_ list. Enable the category_group_name - // for recording. - if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*"))) - category_group_disabled = false; - } - // One of the categories present in category_group_name is not present in - // excluded_ list. Implies this category_group_name group can be enabled - // for recording, since one of its groups is enabled for recording. - if (!category_group_disabled) - break; - } - // If the category group is not excluded, and there are no included patterns - // we consider this category group enabled, as long as it had categories - // other than disabled-by-default. - return !category_group_disabled && had_enabled_by_default && - included_categories_.empty(); -} - -bool TraceConfigCategoryFilter::IsCategoryEnabled( - const StringPiece& category_name) const { - // Check the disabled- filters and the disabled-* wildcard first so that a - // "*" filter does not include the disabled. - for (const std::string& category : disabled_categories_) { - if (MatchPattern(category_name, category)) - return true; - } - - if (MatchPattern(category_name, TRACE_DISABLED_BY_DEFAULT("*"))) - return false; - - for (const std::string& category : included_categories_) { - if (MatchPattern(category_name, category)) - return true; - } - - return false; -} - -void TraceConfigCategoryFilter::Merge(const TraceConfigCategoryFilter& config) { - // Keep included patterns only if both filters have an included entry. - // Otherwise, one of the filter was specifying "*" and we want to honor the - // broadest filter. - if (!included_categories_.empty() && !config.included_categories_.empty()) { - included_categories_.insert(included_categories_.end(), - config.included_categories_.begin(), - config.included_categories_.end()); - } else { - included_categories_.clear(); - } - - disabled_categories_.insert(disabled_categories_.end(), - config.disabled_categories_.begin(), - config.disabled_categories_.end()); - excluded_categories_.insert(excluded_categories_.end(), - config.excluded_categories_.begin(), - config.excluded_categories_.end()); - synthetic_delays_.insert(synthetic_delays_.end(), - config.synthetic_delays_.begin(), - config.synthetic_delays_.end()); -} - -void TraceConfigCategoryFilter::Clear() { - included_categories_.clear(); - disabled_categories_.clear(); - excluded_categories_.clear(); - synthetic_delays_.clear(); -} - -void TraceConfigCategoryFilter::ToDict(DictionaryValue* dict) const { - StringList categories(included_categories_); - categories.insert(categories.end(), disabled_categories_.begin(), - disabled_categories_.end()); - AddCategoriesToDict(categories, kIncludedCategoriesParam, dict); - AddCategoriesToDict(excluded_categories_, kExcludedCategoriesParam, dict); - AddCategoriesToDict(synthetic_delays_, kSyntheticDelaysParam, dict); -} - -std::string TraceConfigCategoryFilter::ToFilterString() const { - std::string filter_string; - WriteCategoryFilterString(included_categories_, &filter_string, true); - WriteCategoryFilterString(disabled_categories_, &filter_string, true); - WriteCategoryFilterString(excluded_categories_, &filter_string, false); - WriteCategoryFilterString(synthetic_delays_, &filter_string); - return filter_string; -} - -void TraceConfigCategoryFilter::SetCategoriesFromIncludedList( - const ListValue& included_list) { - included_categories_.clear(); - for (size_t i = 0; i < included_list.GetSize(); ++i) { - std::string category; - if (!included_list.GetString(i, &category)) - continue; - if (category.compare(0, strlen(TRACE_DISABLED_BY_DEFAULT("")), - TRACE_DISABLED_BY_DEFAULT("")) == 0) { - disabled_categories_.push_back(category); - } else { - included_categories_.push_back(category); - } - } -} - -void TraceConfigCategoryFilter::SetCategoriesFromExcludedList( - const ListValue& excluded_list) { - excluded_categories_.clear(); - for (size_t i = 0; i < excluded_list.GetSize(); ++i) { - std::string category; - if (excluded_list.GetString(i, &category)) - excluded_categories_.push_back(category); - } -} - -void TraceConfigCategoryFilter::SetSyntheticDelaysFromList( - const ListValue& list) { - for (size_t i = 0; i < list.GetSize(); ++i) { - std::string delay; - if (!list.GetString(i, &delay)) - continue; - // Synthetic delays are of the form "delay;option;option;...". - size_t name_length = delay.find(';'); - if (name_length != std::string::npos && name_length > 0 && - name_length != delay.size() - 1) { - synthetic_delays_.push_back(delay); - } - } -} - -void TraceConfigCategoryFilter::AddCategoriesToDict( - const StringList& categories, - const char* param, - DictionaryValue* dict) const { - if (categories.empty()) - return; - - auto list = MakeUnique(); - for (const std::string& category : categories) - list->AppendString(category); - dict->Set(param, std::move(list)); -} - -void TraceConfigCategoryFilter::WriteCategoryFilterString( - const StringList& values, - std::string* out, - bool included) const { - bool prepend_comma = !out->empty(); - int token_cnt = 0; - for (const std::string& category : values) { - if (token_cnt > 0 || prepend_comma) - StringAppendF(out, ","); - StringAppendF(out, "%s%s", (included ? "" : "-"), category.c_str()); - ++token_cnt; - } -} - -void TraceConfigCategoryFilter::WriteCategoryFilterString( - const StringList& delays, - std::string* out) const { - bool prepend_comma = !out->empty(); - int token_cnt = 0; - for (const std::string& category : delays) { - if (token_cnt > 0 || prepend_comma) - StringAppendF(out, ","); - StringAppendF(out, "%s%s)", kSyntheticDelayCategoryFilterPrefix, - category.c_str()); - ++token_cnt; - } -} - -// static -bool TraceConfigCategoryFilter::IsCategoryNameAllowed(StringPiece str) { - return !str.empty() && str.front() != ' ' && str.back() != ' '; -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/trace_config_category_filter.h b/base/trace_event/trace_config_category_filter.h deleted file mode 100644 index 0d7dba0374ebe3767e098bc18ab3a938d336a287..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_config_category_filter.h +++ /dev/null @@ -1,86 +0,0 @@ -// 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. - -#ifndef BASE_TRACE_EVENT_TRACE_CONFIG_CATEGORY_FILTER_H_ -#define BASE_TRACE_EVENT_TRACE_CONFIG_CATEGORY_FILTER_H_ - -#include -#include - -#include "base/base_export.h" -#include "base/strings/string_piece.h" -#include "base/values.h" - -namespace base { -namespace trace_event { - -// Configuration of categories enabled and disabled in TraceConfig. -class BASE_EXPORT TraceConfigCategoryFilter { - public: - using StringList = std::vector; - - TraceConfigCategoryFilter(); - TraceConfigCategoryFilter(const TraceConfigCategoryFilter& other); - ~TraceConfigCategoryFilter(); - - TraceConfigCategoryFilter& operator=(const TraceConfigCategoryFilter& rhs); - - // Initializes from category filter string. See TraceConfig constructor for - // description of how to write category filter string. - void InitializeFromString(const StringPiece& category_filter_string); - - // Initializes TraceConfigCategoryFilter object from the config dictionary. - void InitializeFromConfigDict(const DictionaryValue& dict); - - // Merges this with category filter config. - void Merge(const TraceConfigCategoryFilter& config); - void Clear(); - - // Returns true if at least one category in the list is enabled by this - // trace config. This is used to determine if the category filters are - // enabled in the TRACE_* macros. - bool IsCategoryGroupEnabled(const StringPiece& category_group_name) const; - - // Returns true if the category is enabled according to this trace config. - // This tells whether a category is enabled from the TraceConfig's - // perspective. Please refer to IsCategoryGroupEnabled() to determine if a - // category is enabled from the tracing runtime's perspective. - bool IsCategoryEnabled(const StringPiece& category_name) const; - - void ToDict(DictionaryValue* dict) const; - - std::string ToFilterString() const; - - // Returns true if category name is a valid string. - static bool IsCategoryNameAllowed(StringPiece str); - - const StringList& included_categories() const { return included_categories_; } - const StringList& excluded_categories() const { return excluded_categories_; } - const StringList& synthetic_delays() const { return synthetic_delays_; } - - private: - void SetCategoriesFromIncludedList(const ListValue& included_list); - void SetCategoriesFromExcludedList(const ListValue& excluded_list); - void SetSyntheticDelaysFromList(const ListValue& list); - - void AddCategoriesToDict(const StringList& categories, - const char* param, - DictionaryValue* dict) const; - - void WriteCategoryFilterString(const StringList& values, - std::string* out, - bool included) const; - void WriteCategoryFilterString(const StringList& delays, - std::string* out) const; - - StringList included_categories_; - StringList disabled_categories_; - StringList excluded_categories_; - StringList synthetic_delays_; -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_CONFIG_CATEGORY_FILTER_H_ diff --git a/base/trace_event/trace_config_memory_test_util.h b/base/trace_event/trace_config_memory_test_util.h deleted file mode 100644 index 744e8a8acc1ea194b2fe6b96f1c99e27e79372f7..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_config_memory_test_util.h +++ /dev/null @@ -1,160 +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. - -#ifndef BASE_TRACE_EVENT_TRACE_CONFIG_MEMORY_TEST_UTIL_H_ -#define BASE_TRACE_EVENT_TRACE_CONFIG_MEMORY_TEST_UTIL_H_ - -#include "base/strings/stringprintf.h" -#include "base/trace_event/memory_dump_manager.h" - -namespace base { -namespace trace_event { - -class TraceConfigMemoryTestUtil { - public: - static std::string GetTraceConfig_LegacyPeriodicTriggers(int light_period, - int heavy_period) { - return StringPrintf( - "{" - "\"enable_argument_filter\":false," - "\"enable_systrace\":false," - "\"included_categories\":[" - "\"%s\"" - "]," - "\"memory_dump_config\":{" - "\"allowed_dump_modes\":[\"background\",\"light\",\"detailed\"]," - "\"heap_profiler_options\":{" - "\"breakdown_threshold_bytes\":2048" - "}," - "\"triggers\":[" - "{" - "\"mode\":\"light\"," - "\"periodic_interval_ms\":%d" - "}," - "{" - "\"mode\":\"detailed\"," - "\"periodic_interval_ms\":%d" - "}" - "]" - "}," - "\"record_mode\":\"record-until-full\"" - "}", - MemoryDumpManager::kTraceCategory, light_period, heavy_period); - ; - } - - static std::string GetTraceConfig_PeriodicTriggers(int light_period, - int heavy_period) { - return StringPrintf( - "{" - "\"enable_argument_filter\":false," - "\"enable_systrace\":false," - "\"included_categories\":[" - "\"%s\"" - "]," - "\"memory_dump_config\":{" - "\"allowed_dump_modes\":[\"background\",\"light\",\"detailed\"]," - "\"heap_profiler_options\":{" - "\"breakdown_threshold_bytes\":2048" - "}," - "\"triggers\":[" - "{" - "\"min_time_between_dumps_ms\":%d," - "\"mode\":\"light\"," - "\"type\":\"periodic_interval\"" - "}," - "{" - "\"min_time_between_dumps_ms\":%d," - "\"mode\":\"detailed\"," - "\"type\":\"periodic_interval\"" - "}" - "]" - "}," - "\"record_mode\":\"record-until-full\"" - "}", - MemoryDumpManager::kTraceCategory, light_period, heavy_period); - } - - static std::string GetTraceConfig_EmptyTriggers() { - return StringPrintf( - "{" - "\"enable_argument_filter\":false," - "\"enable_systrace\":false," - "\"included_categories\":[" - "\"%s\"" - "]," - "\"memory_dump_config\":{" - "\"allowed_dump_modes\":[\"background\",\"light\",\"detailed\"]," - "\"triggers\":[" - "]" - "}," - "\"record_mode\":\"record-until-full\"" - "}", - MemoryDumpManager::kTraceCategory); - } - - static std::string GetTraceConfig_NoTriggers() { - return StringPrintf( - "{" - "\"enable_argument_filter\":false," - "\"enable_systrace\":false," - "\"included_categories\":[" - "\"%s\"" - "]," - "\"record_mode\":\"record-until-full\"" - "}", - MemoryDumpManager::kTraceCategory); - } - - static std::string GetTraceConfig_BackgroundTrigger(int period_ms) { - return StringPrintf( - "{" - "\"enable_argument_filter\":false," - "\"enable_systrace\":false," - "\"included_categories\":[" - "\"%s\"" - "]," - "\"memory_dump_config\":{" - "\"allowed_dump_modes\":[\"background\"]," - "\"triggers\":[" - "{" - "\"min_time_between_dumps_ms\":%d," - "\"mode\":\"background\"," - "\"type\":\"periodic_interval\"" - "}" - "]" - "}," - "\"record_mode\":\"record-until-full\"" - "}", - MemoryDumpManager::kTraceCategory, period_ms); - } - - static std::string GetTraceConfig_PeakDetectionTrigger(int heavy_period) { - return StringPrintf( - "{" - "\"enable_argument_filter\":false," - "\"enable_systrace\":false," - "\"included_categories\":[" - "\"%s\"" - "]," - "\"memory_dump_config\":{" - "\"allowed_dump_modes\":[\"background\",\"light\",\"detailed\"]," - "\"triggers\":[" - "{" - "\"min_time_between_dumps_ms\":%d," - "\"mode\":\"detailed\"," - "\"type\":\"peak_memory_usage\"" - "}" - "]" - "}," - "\"record_mode\":\"record-until-full\"" - "}", - MemoryDumpManager::kTraceCategory, heavy_period); - } -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_CONFIG_MEMORY_TEST_UTIL_H_ diff --git a/base/trace_event/trace_config_unittest.cc b/base/trace_event/trace_config_unittest.cc deleted file mode 100644 index a856c2719272b3a0f00242afc1a97cb0c163ae1d..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_config_unittest.cc +++ /dev/null @@ -1,708 +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. - -#include - -#include "base/json/json_reader.h" -#include "base/json/json_writer.h" -#include "base/macros.h" -#include "base/trace_event/memory_dump_manager.h" -#include "base/trace_event/trace_config.h" -#include "base/trace_event/trace_config_memory_test_util.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace trace_event { - -namespace { - -const char kDefaultTraceConfigString[] = - "{" - "\"enable_argument_filter\":false," - "\"enable_systrace\":false," - "\"record_mode\":\"record-until-full\"" - "}"; - -const char kCustomTraceConfigString[] = - "{" - "\"enable_argument_filter\":true," - "\"enable_systrace\":true," - "\"event_filters\":[" - "{" - "\"excluded_categories\":[\"unfiltered_cat\"]," - "\"filter_args\":{\"event_name_whitelist\":[\"a snake\",\"a dog\"]}," - "\"filter_predicate\":\"event_whitelist_predicate\"," - "\"included_categories\":[\"*\"]" - "}" - "]," - "\"excluded_categories\":[\"excluded\",\"exc_pattern*\"]," - "\"included_categories\":[" - "\"included\"," - "\"inc_pattern*\"," - "\"disabled-by-default-cc\"," - "\"disabled-by-default-memory-infra\"]," - "\"memory_dump_config\":{" - "\"allowed_dump_modes\":[\"background\",\"light\",\"detailed\"]," - "\"heap_profiler_options\":{" - "\"breakdown_threshold_bytes\":10240" - "}," - "\"triggers\":[" - "{" - "\"min_time_between_dumps_ms\":50," - "\"mode\":\"light\"," - "\"type\":\"periodic_interval\"" - "}," - "{" - "\"min_time_between_dumps_ms\":1000," - "\"mode\":\"detailed\"," - "\"type\":\"peak_memory_usage\"" - "}" - "]" - "}," - "\"record_mode\":\"record-continuously\"," - "\"synthetic_delays\":[\"test.Delay1;16\",\"test.Delay2;32\"]" - "}"; - -void CheckDefaultTraceConfigBehavior(const TraceConfig& tc) { - EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode()); - EXPECT_FALSE(tc.IsSystraceEnabled()); - EXPECT_FALSE(tc.IsArgumentFilterEnabled()); - - // Default trace config enables every category filter except the - // disabled-by-default-* ones. - EXPECT_TRUE(tc.IsCategoryGroupEnabled("Category1")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("not-excluded-category")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("disabled-by-default-cc")); - - EXPECT_TRUE(tc.IsCategoryGroupEnabled("Category1,not-excluded-category")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("Category1,disabled-by-default-cc")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled( - "disabled-by-default-cc,disabled-by-default-cc2")); -} - -} // namespace - -TEST(TraceConfigTest, TraceConfigFromValidLegacyFormat) { - // From trace options strings - TraceConfig config("", "record-until-full"); - EXPECT_EQ(RECORD_UNTIL_FULL, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("record-until-full", config.ToTraceOptionsString().c_str()); - - config = TraceConfig("", "record-continuously"); - EXPECT_EQ(RECORD_CONTINUOUSLY, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("record-continuously", config.ToTraceOptionsString().c_str()); - - config = TraceConfig("", "trace-to-console"); - EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("trace-to-console", config.ToTraceOptionsString().c_str()); - - config = TraceConfig("", "record-as-much-as-possible"); - EXPECT_EQ(RECORD_AS_MUCH_AS_POSSIBLE, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("record-as-much-as-possible", - config.ToTraceOptionsString().c_str()); - - config = TraceConfig("", "enable-systrace, record-continuously"); - EXPECT_EQ(RECORD_CONTINUOUSLY, config.GetTraceRecordMode()); - EXPECT_TRUE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("record-continuously,enable-systrace", - config.ToTraceOptionsString().c_str()); - - config = TraceConfig("", "enable-argument-filter,record-as-much-as-possible"); - EXPECT_EQ(RECORD_AS_MUCH_AS_POSSIBLE, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_TRUE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("record-as-much-as-possible,enable-argument-filter", - config.ToTraceOptionsString().c_str()); - - config = TraceConfig( - "", - "enable-systrace,trace-to-console,enable-argument-filter"); - EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode()); - EXPECT_TRUE(config.IsSystraceEnabled()); - EXPECT_TRUE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ( - "trace-to-console,enable-systrace,enable-argument-filter", - config.ToTraceOptionsString().c_str()); - - config = TraceConfig( - "", "record-continuously, record-until-full, trace-to-console"); - EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("trace-to-console", config.ToTraceOptionsString().c_str()); - - // From TraceRecordMode - config = TraceConfig("", RECORD_UNTIL_FULL); - EXPECT_EQ(RECORD_UNTIL_FULL, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("record-until-full", config.ToTraceOptionsString().c_str()); - - config = TraceConfig("", RECORD_CONTINUOUSLY); - EXPECT_EQ(RECORD_CONTINUOUSLY, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("record-continuously", config.ToTraceOptionsString().c_str()); - - config = TraceConfig("", ECHO_TO_CONSOLE); - EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("trace-to-console", config.ToTraceOptionsString().c_str()); - - config = TraceConfig("", RECORD_AS_MUCH_AS_POSSIBLE); - EXPECT_EQ(RECORD_AS_MUCH_AS_POSSIBLE, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("record-as-much-as-possible", - config.ToTraceOptionsString().c_str()); - - // From category filter strings - config = TraceConfig("included,-excluded,inc_pattern*,-exc_pattern*", ""); - EXPECT_STREQ("included,inc_pattern*,-excluded,-exc_pattern*", - config.ToCategoryFilterString().c_str()); - - config = TraceConfig("only_inc_cat", ""); - EXPECT_STREQ("only_inc_cat", config.ToCategoryFilterString().c_str()); - - config = TraceConfig("-only_exc_cat", ""); - EXPECT_STREQ("-only_exc_cat", config.ToCategoryFilterString().c_str()); - - config = TraceConfig("disabled-by-default-cc,-excluded", ""); - EXPECT_STREQ("disabled-by-default-cc,-excluded", - config.ToCategoryFilterString().c_str()); - - config = TraceConfig("disabled-by-default-cc,included", ""); - EXPECT_STREQ("included,disabled-by-default-cc", - config.ToCategoryFilterString().c_str()); - - config = TraceConfig("DELAY(test.Delay1;16),included", ""); - EXPECT_STREQ("included,DELAY(test.Delay1;16)", - config.ToCategoryFilterString().c_str()); - - // From both trace options and category filter strings - config = TraceConfig("", ""); - EXPECT_EQ(RECORD_UNTIL_FULL, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("", config.ToCategoryFilterString().c_str()); - EXPECT_STREQ("record-until-full", config.ToTraceOptionsString().c_str()); - - config = TraceConfig("included,-excluded,inc_pattern*,-exc_pattern*", - "enable-systrace, trace-to-console"); - EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode()); - EXPECT_TRUE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("included,inc_pattern*,-excluded,-exc_pattern*", - config.ToCategoryFilterString().c_str()); - EXPECT_STREQ("trace-to-console,enable-systrace", - config.ToTraceOptionsString().c_str()); - - // From both trace options and category filter strings with spaces. - config = TraceConfig(" included , -excluded, inc_pattern*, ,-exc_pattern* ", - "enable-systrace, ,trace-to-console "); - EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode()); - EXPECT_TRUE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("included,inc_pattern*,-excluded,-exc_pattern*", - config.ToCategoryFilterString().c_str()); - EXPECT_STREQ("trace-to-console,enable-systrace", - config.ToTraceOptionsString().c_str()); - - // From category filter string and TraceRecordMode - config = TraceConfig("included,-excluded,inc_pattern*,-exc_pattern*", - RECORD_CONTINUOUSLY); - EXPECT_EQ(RECORD_CONTINUOUSLY, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("included,inc_pattern*,-excluded,-exc_pattern*", - config.ToCategoryFilterString().c_str()); - EXPECT_STREQ("record-continuously", config.ToTraceOptionsString().c_str()); -} - -TEST(TraceConfigTest, TraceConfigFromInvalidLegacyStrings) { - TraceConfig config("", "foo-bar-baz"); - EXPECT_EQ(RECORD_UNTIL_FULL, config.GetTraceRecordMode()); - EXPECT_FALSE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("", config.ToCategoryFilterString().c_str()); - EXPECT_STREQ("record-until-full", config.ToTraceOptionsString().c_str()); - - config = TraceConfig("arbitrary-category", "foo-bar-baz, enable-systrace"); - EXPECT_EQ(RECORD_UNTIL_FULL, config.GetTraceRecordMode()); - EXPECT_TRUE(config.IsSystraceEnabled()); - EXPECT_FALSE(config.IsArgumentFilterEnabled()); - EXPECT_STREQ("arbitrary-category", config.ToCategoryFilterString().c_str()); - EXPECT_STREQ("record-until-full,enable-systrace", - config.ToTraceOptionsString().c_str()); - - const char* const configs[] = { - "", - "DELAY(", - "DELAY(;", - "DELAY(;)", - "DELAY(test.Delay)", - "DELAY(test.Delay;)" - }; - for (size_t i = 0; i < arraysize(configs); i++) { - TraceConfig tc(configs[i], ""); - EXPECT_EQ(0u, tc.GetSyntheticDelayValues().size()); - } -} - -TEST(TraceConfigTest, ConstructDefaultTraceConfig) { - TraceConfig tc; - EXPECT_STREQ("", tc.ToCategoryFilterString().c_str()); - EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str()); - CheckDefaultTraceConfigBehavior(tc); - - // Constructors from category filter string and trace option string. - TraceConfig tc_asterisk("*", ""); - EXPECT_STREQ("*", tc_asterisk.ToCategoryFilterString().c_str()); - CheckDefaultTraceConfigBehavior(tc_asterisk); - - TraceConfig tc_empty_category_filter("", ""); - EXPECT_STREQ("", tc_empty_category_filter.ToCategoryFilterString().c_str()); - EXPECT_STREQ(kDefaultTraceConfigString, - tc_empty_category_filter.ToString().c_str()); - CheckDefaultTraceConfigBehavior(tc_empty_category_filter); - - // Constructor from JSON formated config string. - TraceConfig tc_empty_json_string(""); - EXPECT_STREQ("", tc_empty_json_string.ToCategoryFilterString().c_str()); - EXPECT_STREQ(kDefaultTraceConfigString, - tc_empty_json_string.ToString().c_str()); - CheckDefaultTraceConfigBehavior(tc_empty_json_string); - - // Constructor from dictionary value. - DictionaryValue dict; - TraceConfig tc_dict(dict); - EXPECT_STREQ("", tc_dict.ToCategoryFilterString().c_str()); - EXPECT_STREQ(kDefaultTraceConfigString, tc_dict.ToString().c_str()); - CheckDefaultTraceConfigBehavior(tc_dict); -} - -TEST(TraceConfigTest, EmptyAndAsteriskCategoryFilterString) { - TraceConfig tc_empty("", ""); - TraceConfig tc_asterisk("*", ""); - - EXPECT_STREQ("", tc_empty.ToCategoryFilterString().c_str()); - EXPECT_STREQ("*", tc_asterisk.ToCategoryFilterString().c_str()); - - // Both fall back to default config. - CheckDefaultTraceConfigBehavior(tc_empty); - CheckDefaultTraceConfigBehavior(tc_asterisk); - - // They differ only for internal checking. - EXPECT_FALSE(tc_empty.category_filter().IsCategoryEnabled("Category1")); - EXPECT_FALSE( - tc_empty.category_filter().IsCategoryEnabled("not-excluded-category")); - EXPECT_TRUE(tc_asterisk.category_filter().IsCategoryEnabled("Category1")); - EXPECT_TRUE( - tc_asterisk.category_filter().IsCategoryEnabled("not-excluded-category")); -} - -TEST(TraceConfigTest, DisabledByDefaultCategoryFilterString) { - TraceConfig tc("foo,disabled-by-default-foo", ""); - EXPECT_STREQ("foo,disabled-by-default-foo", - tc.ToCategoryFilterString().c_str()); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("foo")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-foo")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("bar")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("disabled-by-default-bar")); - - EXPECT_TRUE(tc.event_filters().empty()); - // Enabling only the disabled-by-default-* category means the default ones - // are also enabled. - tc = TraceConfig("disabled-by-default-foo", ""); - EXPECT_STREQ("disabled-by-default-foo", tc.ToCategoryFilterString().c_str()); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-foo")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("foo")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("bar")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("disabled-by-default-bar")); -} - -TEST(TraceConfigTest, TraceConfigFromDict) { - // Passing in empty dictionary will result in default trace config. - DictionaryValue dict; - TraceConfig tc(dict); - EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str()); - EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode()); - EXPECT_FALSE(tc.IsSystraceEnabled()); - EXPECT_FALSE(tc.IsArgumentFilterEnabled()); - EXPECT_STREQ("", tc.ToCategoryFilterString().c_str()); - - std::unique_ptr default_value( - JSONReader::Read(kDefaultTraceConfigString)); - DCHECK(default_value); - const DictionaryValue* default_dict = nullptr; - bool is_dict = default_value->GetAsDictionary(&default_dict); - DCHECK(is_dict); - TraceConfig default_tc(*default_dict); - EXPECT_STREQ(kDefaultTraceConfigString, default_tc.ToString().c_str()); - EXPECT_EQ(RECORD_UNTIL_FULL, default_tc.GetTraceRecordMode()); - EXPECT_FALSE(default_tc.IsSystraceEnabled()); - EXPECT_FALSE(default_tc.IsArgumentFilterEnabled()); - EXPECT_STREQ("", default_tc.ToCategoryFilterString().c_str()); - - std::unique_ptr custom_value( - JSONReader::Read(kCustomTraceConfigString)); - DCHECK(custom_value); - const DictionaryValue* custom_dict = nullptr; - is_dict = custom_value->GetAsDictionary(&custom_dict); - DCHECK(is_dict); - TraceConfig custom_tc(*custom_dict); - EXPECT_STREQ(kCustomTraceConfigString, custom_tc.ToString().c_str()); - EXPECT_EQ(RECORD_CONTINUOUSLY, custom_tc.GetTraceRecordMode()); - EXPECT_TRUE(custom_tc.IsSystraceEnabled()); - EXPECT_TRUE(custom_tc.IsArgumentFilterEnabled()); - EXPECT_STREQ("included,inc_pattern*," - "disabled-by-default-cc,disabled-by-default-memory-infra," - "-excluded,-exc_pattern*," - "DELAY(test.Delay1;16),DELAY(test.Delay2;32)", - custom_tc.ToCategoryFilterString().c_str()); -} - -TEST(TraceConfigTest, TraceConfigFromValidString) { - // Using some non-empty config string. - const char config_string[] = - "{" - "\"enable_argument_filter\":true," - "\"enable_systrace\":true," - "\"event_filters\":[" - "{" - "\"excluded_categories\":[\"unfiltered_cat\"]," - "\"filter_args\":{\"event_name_whitelist\":[\"a snake\",\"a dog\"]}," - "\"filter_predicate\":\"event_whitelist_predicate\"," - "\"included_categories\":[\"*\"]" - "}" - "]," - "\"excluded_categories\":[\"excluded\",\"exc_pattern*\"]," - "\"included_categories\":[\"included\"," - "\"inc_pattern*\"," - "\"disabled-by-default-cc\"]," - "\"record_mode\":\"record-continuously\"," - "\"synthetic_delays\":[\"test.Delay1;16\",\"test.Delay2;32\"]" - "}"; - TraceConfig tc(config_string); - - EXPECT_STREQ(config_string, tc.ToString().c_str()); - EXPECT_EQ(RECORD_CONTINUOUSLY, tc.GetTraceRecordMode()); - EXPECT_TRUE(tc.IsSystraceEnabled()); - EXPECT_TRUE(tc.IsArgumentFilterEnabled()); - EXPECT_STREQ("included,inc_pattern*,disabled-by-default-cc,-excluded," - "-exc_pattern*,DELAY(test.Delay1;16),DELAY(test.Delay2;32)", - tc.ToCategoryFilterString().c_str()); - - EXPECT_TRUE(tc.category_filter().IsCategoryEnabled("included")); - EXPECT_TRUE(tc.category_filter().IsCategoryEnabled("inc_pattern_category")); - EXPECT_TRUE(tc.category_filter().IsCategoryEnabled("disabled-by-default-cc")); - EXPECT_FALSE(tc.category_filter().IsCategoryEnabled("excluded")); - EXPECT_FALSE(tc.category_filter().IsCategoryEnabled("exc_pattern_category")); - EXPECT_FALSE( - tc.category_filter().IsCategoryEnabled("disabled-by-default-others")); - EXPECT_FALSE( - tc.category_filter().IsCategoryEnabled("not-excluded-nor-included")); - - EXPECT_TRUE(tc.IsCategoryGroupEnabled("included")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("inc_pattern_category")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-cc")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("excluded")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("exc_pattern_category")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("disabled-by-default-others")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("not-excluded-nor-included")); - - EXPECT_TRUE(tc.IsCategoryGroupEnabled("included,excluded")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("excluded,exc_pattern_category")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("included,DELAY(test.Delay1;16)")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("DELAY(test.Delay1;16)")); - - EXPECT_EQ(2u, tc.GetSyntheticDelayValues().size()); - EXPECT_STREQ("test.Delay1;16", tc.GetSyntheticDelayValues()[0].c_str()); - EXPECT_STREQ("test.Delay2;32", tc.GetSyntheticDelayValues()[1].c_str()); - - EXPECT_EQ(tc.event_filters().size(), 1u); - const TraceConfig::EventFilterConfig& event_filter = tc.event_filters()[0]; - EXPECT_STREQ("event_whitelist_predicate", - event_filter.predicate_name().c_str()); - EXPECT_EQ(1u, event_filter.category_filter().included_categories().size()); - EXPECT_STREQ("*", - event_filter.category_filter().included_categories()[0].c_str()); - EXPECT_EQ(1u, event_filter.category_filter().excluded_categories().size()); - EXPECT_STREQ("unfiltered_cat", - event_filter.category_filter().excluded_categories()[0].c_str()); - EXPECT_TRUE(event_filter.filter_args()); - - std::string json_out; - base::JSONWriter::Write(*event_filter.filter_args(), &json_out); - EXPECT_STREQ(json_out.c_str(), - "{\"event_name_whitelist\":[\"a snake\",\"a dog\"]}"); - std::unordered_set filter_values; - EXPECT_TRUE(event_filter.GetArgAsSet("event_name_whitelist", &filter_values)); - EXPECT_EQ(2u, filter_values.size()); - EXPECT_EQ(1u, filter_values.count("a snake")); - EXPECT_EQ(1u, filter_values.count("a dog")); - - const char config_string_2[] = "{\"included_categories\":[\"*\"]}"; - TraceConfig tc2(config_string_2); - EXPECT_TRUE(tc2.category_filter().IsCategoryEnabled( - "non-disabled-by-default-pattern")); - EXPECT_FALSE( - tc2.category_filter().IsCategoryEnabled("disabled-by-default-pattern")); - EXPECT_TRUE(tc2.IsCategoryGroupEnabled("non-disabled-by-default-pattern")); - EXPECT_FALSE(tc2.IsCategoryGroupEnabled("disabled-by-default-pattern")); - - // Clear - tc.Clear(); - EXPECT_STREQ(tc.ToString().c_str(), - "{" - "\"enable_argument_filter\":false," - "\"enable_systrace\":false," - "\"record_mode\":\"record-until-full\"" - "}"); -} - -TEST(TraceConfigTest, TraceConfigFromInvalidString) { - // The config string needs to be a dictionary correctly formatted as a JSON - // string. Otherwise, it will fall back to the default initialization. - TraceConfig tc(""); - EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str()); - EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode()); - EXPECT_FALSE(tc.IsSystraceEnabled()); - EXPECT_FALSE(tc.IsArgumentFilterEnabled()); - EXPECT_STREQ("", tc.ToCategoryFilterString().c_str()); - CheckDefaultTraceConfigBehavior(tc); - - tc = TraceConfig("This is an invalid config string."); - EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str()); - EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode()); - EXPECT_FALSE(tc.IsSystraceEnabled()); - EXPECT_FALSE(tc.IsArgumentFilterEnabled()); - EXPECT_STREQ("", tc.ToCategoryFilterString().c_str()); - CheckDefaultTraceConfigBehavior(tc); - - tc = TraceConfig("[\"This\", \"is\", \"not\", \"a\", \"dictionary\"]"); - EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str()); - EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode()); - EXPECT_FALSE(tc.IsSystraceEnabled()); - EXPECT_FALSE(tc.IsArgumentFilterEnabled()); - EXPECT_STREQ("", tc.ToCategoryFilterString().c_str()); - CheckDefaultTraceConfigBehavior(tc); - - tc = TraceConfig("{\"record_mode\": invalid-value-needs-double-quote}"); - EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str()); - EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode()); - EXPECT_FALSE(tc.IsSystraceEnabled()); - EXPECT_FALSE(tc.IsArgumentFilterEnabled()); - EXPECT_STREQ("", tc.ToCategoryFilterString().c_str()); - CheckDefaultTraceConfigBehavior(tc); - - // If the config string a dictionary formatted as a JSON string, it will - // initialize TraceConfig with best effort. - tc = TraceConfig("{}"); - EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode()); - EXPECT_FALSE(tc.IsSystraceEnabled()); - EXPECT_FALSE(tc.IsArgumentFilterEnabled()); - EXPECT_STREQ("", tc.ToCategoryFilterString().c_str()); - CheckDefaultTraceConfigBehavior(tc); - - tc = TraceConfig("{\"arbitrary-key\":\"arbitrary-value\"}"); - EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode()); - EXPECT_FALSE(tc.IsSystraceEnabled()); - EXPECT_FALSE(tc.IsArgumentFilterEnabled()); - EXPECT_STREQ("", tc.ToCategoryFilterString().c_str()); - CheckDefaultTraceConfigBehavior(tc); - - const char invalid_config_string[] = - "{" - "\"enable_systrace\":1," - "\"excluded_categories\":[\"excluded\"]," - "\"included_categories\":\"not a list\"," - "\"record_mode\":\"arbitrary-mode\"," - "\"synthetic_delays\":[\"test.Delay1;16\"," - "\"invalid-delay\"," - "\"test.Delay2;32\"]" - "}"; - tc = TraceConfig(invalid_config_string); - EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode()); - EXPECT_FALSE(tc.IsSystraceEnabled()); - EXPECT_FALSE(tc.IsArgumentFilterEnabled()); - EXPECT_STREQ("-excluded,DELAY(test.Delay1;16),DELAY(test.Delay2;32)", - tc.ToCategoryFilterString().c_str()); - - const char invalid_config_string_2[] = - "{" - "\"included_categories\":[\"category\",\"disabled-by-default-pattern\"]," - "\"excluded_categories\":[\"category\",\"disabled-by-default-pattern\"]" - "}"; - tc = TraceConfig(invalid_config_string_2); - EXPECT_TRUE(tc.category_filter().IsCategoryEnabled("category")); - EXPECT_TRUE( - tc.category_filter().IsCategoryEnabled("disabled-by-default-pattern")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("category")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-pattern")); -} - -TEST(TraceConfigTest, MergingTraceConfigs) { - // Merge - TraceConfig tc; - TraceConfig tc2("included,-excluded,inc_pattern*,-exc_pattern*", ""); - tc.Merge(tc2); - EXPECT_STREQ("{" - "\"enable_argument_filter\":false," - "\"enable_systrace\":false," - "\"excluded_categories\":[\"excluded\",\"exc_pattern*\"]," - "\"record_mode\":\"record-until-full\"" - "}", - tc.ToString().c_str()); - - tc = TraceConfig("DELAY(test.Delay1;16)", ""); - tc2 = TraceConfig("DELAY(test.Delay2;32)", ""); - tc.Merge(tc2); - EXPECT_EQ(2u, tc.GetSyntheticDelayValues().size()); - EXPECT_STREQ("test.Delay1;16", tc.GetSyntheticDelayValues()[0].c_str()); - EXPECT_STREQ("test.Delay2;32", tc.GetSyntheticDelayValues()[1].c_str()); -} - -TEST(TraceConfigTest, IsCategoryGroupEnabled) { - // Enabling a disabled- category does not require all categories to be traced - // to be included. - TraceConfig tc("disabled-by-default-cc,-excluded", ""); - EXPECT_STREQ("disabled-by-default-cc,-excluded", - tc.ToCategoryFilterString().c_str()); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-cc")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("some_other_group")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("excluded")); - - // Enabled a disabled- category and also including makes all categories to - // be traced require including. - tc = TraceConfig("disabled-by-default-cc,included", ""); - EXPECT_STREQ("included,disabled-by-default-cc", - tc.ToCategoryFilterString().c_str()); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-cc")); - EXPECT_TRUE(tc.IsCategoryGroupEnabled("included")); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("other_included")); - - // Excluding categories won't enable disabled-by-default ones with the - // excluded category is also present in the group. - tc = TraceConfig("-excluded", ""); - EXPECT_STREQ("-excluded", tc.ToCategoryFilterString().c_str()); - EXPECT_FALSE(tc.IsCategoryGroupEnabled("excluded,disabled-by-default-cc")); -} - -TEST(TraceConfigTest, IsCategoryNameAllowed) { - // Test that IsCategoryNameAllowed actually catches categories that are - // explicitly forbidden. This method is called in a DCHECK to assert that we - // don't have these types of strings as categories. - EXPECT_FALSE( - TraceConfigCategoryFilter::IsCategoryNameAllowed(" bad_category ")); - EXPECT_FALSE( - TraceConfigCategoryFilter::IsCategoryNameAllowed(" bad_category")); - EXPECT_FALSE( - TraceConfigCategoryFilter::IsCategoryNameAllowed("bad_category ")); - EXPECT_FALSE( - TraceConfigCategoryFilter::IsCategoryNameAllowed(" bad_category")); - EXPECT_FALSE( - TraceConfigCategoryFilter::IsCategoryNameAllowed("bad_category ")); - EXPECT_FALSE( - TraceConfigCategoryFilter::IsCategoryNameAllowed(" bad_category ")); - EXPECT_FALSE(TraceConfigCategoryFilter::IsCategoryNameAllowed("")); - EXPECT_TRUE( - TraceConfigCategoryFilter::IsCategoryNameAllowed("good_category")); -} - -TEST(TraceConfigTest, SetTraceOptionValues) { - TraceConfig tc; - EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode()); - EXPECT_FALSE(tc.IsSystraceEnabled()); - - tc.SetTraceRecordMode(RECORD_AS_MUCH_AS_POSSIBLE); - EXPECT_EQ(RECORD_AS_MUCH_AS_POSSIBLE, tc.GetTraceRecordMode()); - - tc.EnableSystrace(); - EXPECT_TRUE(tc.IsSystraceEnabled()); -} - -TEST(TraceConfigTest, TraceConfigFromMemoryConfigString) { - std::string tc_str1 = - TraceConfigMemoryTestUtil::GetTraceConfig_PeriodicTriggers(200, 2000); - TraceConfig tc1(tc_str1); - EXPECT_EQ(tc_str1, tc1.ToString()); - TraceConfig tc2( - TraceConfigMemoryTestUtil::GetTraceConfig_LegacyPeriodicTriggers(200, - 2000)); - EXPECT_EQ(tc_str1, tc2.ToString()); - - EXPECT_TRUE(tc1.IsCategoryGroupEnabled(MemoryDumpManager::kTraceCategory)); - ASSERT_EQ(2u, tc1.memory_dump_config().triggers.size()); - - EXPECT_EQ(200u, - tc1.memory_dump_config().triggers[0].min_time_between_dumps_ms); - EXPECT_EQ(MemoryDumpLevelOfDetail::LIGHT, - tc1.memory_dump_config().triggers[0].level_of_detail); - - EXPECT_EQ(2000u, - tc1.memory_dump_config().triggers[1].min_time_between_dumps_ms); - EXPECT_EQ(MemoryDumpLevelOfDetail::DETAILED, - tc1.memory_dump_config().triggers[1].level_of_detail); - EXPECT_EQ( - 2048u, - tc1.memory_dump_config().heap_profiler_options.breakdown_threshold_bytes); - - std::string tc_str3 = - TraceConfigMemoryTestUtil::GetTraceConfig_BackgroundTrigger( - 1 /* period_ms */); - TraceConfig tc3(tc_str3); - EXPECT_EQ(tc_str3, tc3.ToString()); - EXPECT_TRUE(tc3.IsCategoryGroupEnabled(MemoryDumpManager::kTraceCategory)); - ASSERT_EQ(1u, tc3.memory_dump_config().triggers.size()); - EXPECT_EQ(1u, tc3.memory_dump_config().triggers[0].min_time_between_dumps_ms); - EXPECT_EQ(MemoryDumpLevelOfDetail::BACKGROUND, - tc3.memory_dump_config().triggers[0].level_of_detail); - - std::string tc_str4 = - TraceConfigMemoryTestUtil::GetTraceConfig_PeakDetectionTrigger( - 1 /*heavy_period */); - TraceConfig tc4(tc_str4); - EXPECT_EQ(tc_str4, tc4.ToString()); - ASSERT_EQ(1u, tc4.memory_dump_config().triggers.size()); - EXPECT_EQ(1u, tc4.memory_dump_config().triggers[0].min_time_between_dumps_ms); - EXPECT_EQ(MemoryDumpLevelOfDetail::DETAILED, - tc4.memory_dump_config().triggers[0].level_of_detail); -} - -TEST(TraceConfigTest, EmptyMemoryDumpConfigTest) { - // Empty trigger list should also be specified when converting back to string. - TraceConfig tc(TraceConfigMemoryTestUtil::GetTraceConfig_EmptyTriggers()); - EXPECT_EQ(TraceConfigMemoryTestUtil::GetTraceConfig_EmptyTriggers(), - tc.ToString()); - EXPECT_EQ(0u, tc.memory_dump_config().triggers.size()); - EXPECT_EQ( - TraceConfig::MemoryDumpConfig::HeapProfiler :: - kDefaultBreakdownThresholdBytes, - tc.memory_dump_config().heap_profiler_options.breakdown_threshold_bytes); -} - -TEST(TraceConfigTest, LegacyStringToMemoryDumpConfig) { - TraceConfig tc(MemoryDumpManager::kTraceCategory, ""); - EXPECT_TRUE(tc.IsCategoryGroupEnabled(MemoryDumpManager::kTraceCategory)); - EXPECT_NE(std::string::npos, tc.ToString().find("memory_dump_config")); - EXPECT_EQ(2u, tc.memory_dump_config().triggers.size()); - EXPECT_EQ( - TraceConfig::MemoryDumpConfig::HeapProfiler :: - kDefaultBreakdownThresholdBytes, - tc.memory_dump_config().heap_profiler_options.breakdown_threshold_bytes); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/trace_event.h b/base/trace_event/trace_event.h index 51e6927cbd5b1724821e77f41d848e94f11f59ad..9abb8887b04050dcc3126cfb4f9361ab7af49780 100644 --- a/base/trace_event/trace_event.h +++ b/base/trace_event/trace_event.h @@ -5,6 +5,43 @@ #ifndef BASE_TRACE_EVENT_TRACE_EVENT_H_ #define BASE_TRACE_EVENT_TRACE_EVENT_H_ +// Replace with stub implementation. +#if 1 +#include "base/trace_event/common/trace_event_common.h" +#include "base/trace_event/heap_profiler.h" + +// To avoid -Wunused-* errors, eat expression by macro. +namespace libchrome_internal { +template void Ignore(Args&&... args) {} +} +#define INTERNAL_IGNORE(...) \ + (false ? libchrome_internal::Ignore(__VA_ARGS__) : (void) 0) + +// Body is effectively empty. +#define INTERNAL_TRACE_EVENT_ADD_SCOPED(...) INTERNAL_IGNORE(__VA_ARGS__) +#define INTERNAL_TRACE_TASK_EXECUTION(...) +#define INTERNAL_TRACE_EVENT_ADD_SCOPED_WITH_FLOW(...) \ + INTERNAL_IGNORE(__VA_ARGS__) +#define TRACE_ID_MANGLE(val) (val) + +namespace base { +namespace trace_event { + +class TraceLog { + public: + static TraceLog* GetInstance() { + static TraceLog instance; + return &instance; + } + + pid_t process_id() { return 0; } + void SetCurrentThreadBlocksMessageLoop() {} +}; + +} // namespace trace_event +} // namespace base +#else + // This header file defines implementation details of how the trace macros in // trace_event_common.h collect and store trace events. Anything not // implementation-specific should go in trace_event_common.h instead of here. @@ -1115,4 +1152,5 @@ template class TraceScopedTrackableObject { } // namespace trace_event } // namespace base +#endif #endif // BASE_TRACE_EVENT_TRACE_EVENT_H_ diff --git a/base/trace_event/trace_event_argument.cc b/base/trace_event/trace_event_argument.cc deleted file mode 100644 index db702b6231e5249c9526d27ac439450c732e39a6..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_argument.cc +++ /dev/null @@ -1,473 +0,0 @@ -// Copyright (c) 2014 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. - -#include "base/trace_event/trace_event_argument.h" - -#include - -#include - -#include "base/bits.h" -#include "base/json/json_writer.h" -#include "base/memory/ptr_util.h" -#include "base/trace_event/trace_event_memory_overhead.h" -#include "base/values.h" - -namespace base { -namespace trace_event { - -namespace { -const char kTypeStartDict = '{'; -const char kTypeEndDict = '}'; -const char kTypeStartArray = '['; -const char kTypeEndArray = ']'; -const char kTypeBool = 'b'; -const char kTypeInt = 'i'; -const char kTypeDouble = 'd'; -const char kTypeString = 's'; -const char kTypeCStr = '*'; - -#ifndef NDEBUG -const bool kStackTypeDict = false; -const bool kStackTypeArray = true; -#define DCHECK_CURRENT_CONTAINER_IS(x) DCHECK_EQ(x, nesting_stack_.back()) -#define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) DCHECK_EQ(x, nesting_stack_.size()) -#define DEBUG_PUSH_CONTAINER(x) nesting_stack_.push_back(x) -#define DEBUG_POP_CONTAINER() nesting_stack_.pop_back() -#else -#define DCHECK_CURRENT_CONTAINER_IS(x) do {} while (0) -#define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) do {} while (0) -#define DEBUG_PUSH_CONTAINER(x) do {} while (0) -#define DEBUG_POP_CONTAINER() do {} while (0) -#endif - -inline void WriteKeyNameAsRawPtr(Pickle& pickle, const char* ptr) { - pickle.WriteBytes(&kTypeCStr, 1); - pickle.WriteUInt64(static_cast(reinterpret_cast(ptr))); -} - -inline void WriteKeyNameWithCopy(Pickle& pickle, base::StringPiece str) { - pickle.WriteBytes(&kTypeString, 1); - pickle.WriteString(str); -} - -std::string ReadKeyName(PickleIterator& pickle_iterator) { - const char* type = nullptr; - bool res = pickle_iterator.ReadBytes(&type, 1); - std::string key_name; - if (res && *type == kTypeCStr) { - uint64_t ptr_value = 0; - res = pickle_iterator.ReadUInt64(&ptr_value); - key_name = reinterpret_cast(static_cast(ptr_value)); - } else if (res && *type == kTypeString) { - res = pickle_iterator.ReadString(&key_name); - } - DCHECK(res); - return key_name; -} -} // namespace - -TracedValue::TracedValue() : TracedValue(0) { -} - -TracedValue::TracedValue(size_t capacity) { - DEBUG_PUSH_CONTAINER(kStackTypeDict); - if (capacity) - pickle_.Reserve(capacity); -} - -TracedValue::~TracedValue() { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - DEBUG_POP_CONTAINER(); - DCHECK_CONTAINER_STACK_DEPTH_EQ(0u); -} - -void TracedValue::SetInteger(const char* name, int value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - pickle_.WriteBytes(&kTypeInt, 1); - pickle_.WriteInt(value); - WriteKeyNameAsRawPtr(pickle_, name); -} - -void TracedValue::SetIntegerWithCopiedName(base::StringPiece name, int value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - pickle_.WriteBytes(&kTypeInt, 1); - pickle_.WriteInt(value); - WriteKeyNameWithCopy(pickle_, name); -} - -void TracedValue::SetDouble(const char* name, double value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - pickle_.WriteBytes(&kTypeDouble, 1); - pickle_.WriteDouble(value); - WriteKeyNameAsRawPtr(pickle_, name); -} - -void TracedValue::SetDoubleWithCopiedName(base::StringPiece name, - double value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - pickle_.WriteBytes(&kTypeDouble, 1); - pickle_.WriteDouble(value); - WriteKeyNameWithCopy(pickle_, name); -} - -void TracedValue::SetBoolean(const char* name, bool value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - pickle_.WriteBytes(&kTypeBool, 1); - pickle_.WriteBool(value); - WriteKeyNameAsRawPtr(pickle_, name); -} - -void TracedValue::SetBooleanWithCopiedName(base::StringPiece name, - bool value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - pickle_.WriteBytes(&kTypeBool, 1); - pickle_.WriteBool(value); - WriteKeyNameWithCopy(pickle_, name); -} - -void TracedValue::SetString(const char* name, base::StringPiece value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - pickle_.WriteBytes(&kTypeString, 1); - pickle_.WriteString(value); - WriteKeyNameAsRawPtr(pickle_, name); -} - -void TracedValue::SetStringWithCopiedName(base::StringPiece name, - base::StringPiece value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - pickle_.WriteBytes(&kTypeString, 1); - pickle_.WriteString(value); - WriteKeyNameWithCopy(pickle_, name); -} - -void TracedValue::SetValue(const char* name, const TracedValue& value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - BeginDictionary(name); - pickle_.WriteBytes(value.pickle_.payload(), - static_cast(value.pickle_.payload_size())); - EndDictionary(); -} - -void TracedValue::SetValueWithCopiedName(base::StringPiece name, - const TracedValue& value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - BeginDictionaryWithCopiedName(name); - pickle_.WriteBytes(value.pickle_.payload(), - static_cast(value.pickle_.payload_size())); - EndDictionary(); -} - -void TracedValue::BeginDictionary(const char* name) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - DEBUG_PUSH_CONTAINER(kStackTypeDict); - pickle_.WriteBytes(&kTypeStartDict, 1); - WriteKeyNameAsRawPtr(pickle_, name); -} - -void TracedValue::BeginDictionaryWithCopiedName(base::StringPiece name) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - DEBUG_PUSH_CONTAINER(kStackTypeDict); - pickle_.WriteBytes(&kTypeStartDict, 1); - WriteKeyNameWithCopy(pickle_, name); -} - -void TracedValue::BeginArray(const char* name) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - DEBUG_PUSH_CONTAINER(kStackTypeArray); - pickle_.WriteBytes(&kTypeStartArray, 1); - WriteKeyNameAsRawPtr(pickle_, name); -} - -void TracedValue::BeginArrayWithCopiedName(base::StringPiece name) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - DEBUG_PUSH_CONTAINER(kStackTypeArray); - pickle_.WriteBytes(&kTypeStartArray, 1); - WriteKeyNameWithCopy(pickle_, name); -} - -void TracedValue::EndDictionary() { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - DEBUG_POP_CONTAINER(); - pickle_.WriteBytes(&kTypeEndDict, 1); -} - -void TracedValue::AppendInteger(int value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); - pickle_.WriteBytes(&kTypeInt, 1); - pickle_.WriteInt(value); -} - -void TracedValue::AppendDouble(double value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); - pickle_.WriteBytes(&kTypeDouble, 1); - pickle_.WriteDouble(value); -} - -void TracedValue::AppendBoolean(bool value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); - pickle_.WriteBytes(&kTypeBool, 1); - pickle_.WriteBool(value); -} - -void TracedValue::AppendString(base::StringPiece value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); - pickle_.WriteBytes(&kTypeString, 1); - pickle_.WriteString(value); -} - -void TracedValue::BeginArray() { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); - DEBUG_PUSH_CONTAINER(kStackTypeArray); - pickle_.WriteBytes(&kTypeStartArray, 1); -} - -void TracedValue::BeginDictionary() { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); - DEBUG_PUSH_CONTAINER(kStackTypeDict); - pickle_.WriteBytes(&kTypeStartDict, 1); -} - -void TracedValue::EndArray() { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); - DEBUG_POP_CONTAINER(); - pickle_.WriteBytes(&kTypeEndArray, 1); -} - -void TracedValue::SetValue(const char* name, - std::unique_ptr value) { - SetBaseValueWithCopiedName(name, *value); -} - -void TracedValue::SetBaseValueWithCopiedName(base::StringPiece name, - const base::Value& value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - switch (value.GetType()) { - case base::Value::Type::NONE: - case base::Value::Type::BINARY: - NOTREACHED(); - break; - - case base::Value::Type::BOOLEAN: { - bool bool_value; - value.GetAsBoolean(&bool_value); - SetBooleanWithCopiedName(name, bool_value); - } break; - - case base::Value::Type::INTEGER: { - int int_value; - value.GetAsInteger(&int_value); - SetIntegerWithCopiedName(name, int_value); - } break; - - case base::Value::Type::DOUBLE: { - double double_value; - value.GetAsDouble(&double_value); - SetDoubleWithCopiedName(name, double_value); - } break; - - case base::Value::Type::STRING: { - const Value* string_value; - value.GetAsString(&string_value); - SetStringWithCopiedName(name, string_value->GetString()); - } break; - - case base::Value::Type::DICTIONARY: { - const DictionaryValue* dict_value; - value.GetAsDictionary(&dict_value); - BeginDictionaryWithCopiedName(name); - for (DictionaryValue::Iterator it(*dict_value); !it.IsAtEnd(); - it.Advance()) { - SetBaseValueWithCopiedName(it.key(), it.value()); - } - EndDictionary(); - } break; - - case base::Value::Type::LIST: { - const ListValue* list_value; - value.GetAsList(&list_value); - BeginArrayWithCopiedName(name); - for (const auto& base_value : *list_value) - AppendBaseValue(*base_value); - EndArray(); - } break; - } -} - -void TracedValue::AppendBaseValue(const base::Value& value) { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); - switch (value.GetType()) { - case base::Value::Type::NONE: - case base::Value::Type::BINARY: - NOTREACHED(); - break; - - case base::Value::Type::BOOLEAN: { - bool bool_value; - value.GetAsBoolean(&bool_value); - AppendBoolean(bool_value); - } break; - - case base::Value::Type::INTEGER: { - int int_value; - value.GetAsInteger(&int_value); - AppendInteger(int_value); - } break; - - case base::Value::Type::DOUBLE: { - double double_value; - value.GetAsDouble(&double_value); - AppendDouble(double_value); - } break; - - case base::Value::Type::STRING: { - const Value* string_value; - value.GetAsString(&string_value); - AppendString(string_value->GetString()); - } break; - - case base::Value::Type::DICTIONARY: { - const DictionaryValue* dict_value; - value.GetAsDictionary(&dict_value); - BeginDictionary(); - for (DictionaryValue::Iterator it(*dict_value); !it.IsAtEnd(); - it.Advance()) { - SetBaseValueWithCopiedName(it.key(), it.value()); - } - EndDictionary(); - } break; - - case base::Value::Type::LIST: { - const ListValue* list_value; - value.GetAsList(&list_value); - BeginArray(); - for (const auto& base_value : *list_value) - AppendBaseValue(*base_value); - EndArray(); - } break; - } -} - -std::unique_ptr TracedValue::ToBaseValue() const { - std::unique_ptr root(new DictionaryValue); - DictionaryValue* cur_dict = root.get(); - ListValue* cur_list = nullptr; - std::vector stack; - PickleIterator it(pickle_); - const char* type; - - while (it.ReadBytes(&type, 1)) { - DCHECK((cur_dict && !cur_list) || (cur_list && !cur_dict)); - switch (*type) { - case kTypeStartDict: { - auto* new_dict = new DictionaryValue(); - if (cur_dict) { - cur_dict->SetWithoutPathExpansion(ReadKeyName(it), - WrapUnique(new_dict)); - stack.push_back(cur_dict); - cur_dict = new_dict; - } else { - cur_list->Append(WrapUnique(new_dict)); - stack.push_back(cur_list); - cur_list = nullptr; - cur_dict = new_dict; - } - } break; - - case kTypeEndArray: - case kTypeEndDict: { - if (stack.back()->GetAsDictionary(&cur_dict)) { - cur_list = nullptr; - } else if (stack.back()->GetAsList(&cur_list)) { - cur_dict = nullptr; - } - stack.pop_back(); - } break; - - case kTypeStartArray: { - auto* new_list = new ListValue(); - if (cur_dict) { - cur_dict->SetWithoutPathExpansion(ReadKeyName(it), - WrapUnique(new_list)); - stack.push_back(cur_dict); - cur_dict = nullptr; - cur_list = new_list; - } else { - cur_list->Append(WrapUnique(new_list)); - stack.push_back(cur_list); - cur_list = new_list; - } - } break; - - case kTypeBool: { - bool value; - CHECK(it.ReadBool(&value)); - if (cur_dict) { - cur_dict->SetBooleanWithoutPathExpansion(ReadKeyName(it), value); - } else { - cur_list->AppendBoolean(value); - } - } break; - - case kTypeInt: { - int value; - CHECK(it.ReadInt(&value)); - if (cur_dict) { - cur_dict->SetIntegerWithoutPathExpansion(ReadKeyName(it), value); - } else { - cur_list->AppendInteger(value); - } - } break; - - case kTypeDouble: { - double value; - CHECK(it.ReadDouble(&value)); - if (cur_dict) { - cur_dict->SetDoubleWithoutPathExpansion(ReadKeyName(it), value); - } else { - cur_list->AppendDouble(value); - } - } break; - - case kTypeString: { - std::string value; - CHECK(it.ReadString(&value)); - if (cur_dict) { - cur_dict->SetStringWithoutPathExpansion(ReadKeyName(it), value); - } else { - cur_list->AppendString(value); - } - } break; - - default: - NOTREACHED(); - } - } - DCHECK(stack.empty()); - return std::move(root); -} - -void TracedValue::AppendAsTraceFormat(std::string* out) const { - DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); - DCHECK_CONTAINER_STACK_DEPTH_EQ(1u); - - // TODO(primiano): this could be smarter, skip the ToBaseValue encoding and - // produce the JSON on its own. This will require refactoring JSONWriter - // to decouple the base::Value traversal from the JSON writing bits - std::string tmp; - JSONWriter::Write(*ToBaseValue(), &tmp); - *out += tmp; -} - -void TracedValue::EstimateTraceMemoryOverhead( - TraceEventMemoryOverhead* overhead) { - overhead->Add("TracedValue", - /* allocated size */ - pickle_.GetTotalAllocatedSize(), - /* resident size */ - pickle_.size()); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/trace_event_argument.h b/base/trace_event/trace_event_argument.h deleted file mode 100644 index 81d8c0172a49ed08e0cb378cb5b43bc77f57b08c..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_argument.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2014 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 BASE_TRACE_EVENT_TRACE_EVENT_ARGUMENT_H_ -#define BASE_TRACE_EVENT_TRACE_EVENT_ARGUMENT_H_ - -#include - -#include -#include -#include - -#include "base/macros.h" -#include "base/pickle.h" -#include "base/strings/string_piece.h" -#include "base/trace_event/trace_event_impl.h" - -namespace base { - -class Value; - -namespace trace_event { - -class BASE_EXPORT TracedValue : public ConvertableToTraceFormat { - public: - TracedValue(); - explicit TracedValue(size_t capacity); - ~TracedValue() override; - - void EndDictionary(); - void EndArray(); - - // These methods assume that |name| is a long lived "quoted" string. - void SetInteger(const char* name, int value); - void SetDouble(const char* name, double value); - void SetBoolean(const char* name, bool value); - void SetString(const char* name, base::StringPiece value); - void SetValue(const char* name, const TracedValue& value); - void BeginDictionary(const char* name); - void BeginArray(const char* name); - - // These, instead, can be safely passed a temporary string. - void SetIntegerWithCopiedName(base::StringPiece name, int value); - void SetDoubleWithCopiedName(base::StringPiece name, double value); - void SetBooleanWithCopiedName(base::StringPiece name, bool value); - void SetStringWithCopiedName(base::StringPiece name, - base::StringPiece value); - void SetValueWithCopiedName(base::StringPiece name, - const TracedValue& value); - void BeginDictionaryWithCopiedName(base::StringPiece name); - void BeginArrayWithCopiedName(base::StringPiece name); - - void AppendInteger(int); - void AppendDouble(double); - void AppendBoolean(bool); - void AppendString(base::StringPiece); - void BeginArray(); - void BeginDictionary(); - - // ConvertableToTraceFormat implementation. - void AppendAsTraceFormat(std::string* out) const override; - - void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead) override; - - // DEPRECATED: do not use, here only for legacy reasons. These methods causes - // a copy-and-translation of the base::Value into the equivalent TracedValue. - // TODO(primiano): migrate the (three) existing clients to the cheaper - // SetValue(TracedValue) API. crbug.com/495628. - void SetValue(const char* name, std::unique_ptr value); - void SetBaseValueWithCopiedName(base::StringPiece name, - const base::Value& value); - void AppendBaseValue(const base::Value& value); - - // Public for tests only. - std::unique_ptr ToBaseValue() const; - - private: - Pickle pickle_; - -#ifndef NDEBUG - // In debug builds checks the pairings of {Start,End}{Dictionary,Array} - std::vector nesting_stack_; -#endif - - DISALLOW_COPY_AND_ASSIGN(TracedValue); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_EVENT_ARGUMENT_H_ diff --git a/base/trace_event/trace_event_argument_unittest.cc b/base/trace_event/trace_event_argument_unittest.cc deleted file mode 100644 index aef8441c8e224bd1b1d77b4c03d04a68cd66d3c1..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_argument_unittest.cc +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) 2014 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. - -#include "base/trace_event/trace_event_argument.h" - -#include - -#include - -#include "base/memory/ptr_util.h" -#include "base/values.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace trace_event { - -TEST(TraceEventArgumentTest, FlatDictionary) { - std::unique_ptr value(new TracedValue()); - value->SetInteger("int", 2014); - value->SetDouble("double", 0.0); - value->SetBoolean("bool", true); - value->SetString("string", "string"); - std::string json = "PREFIX"; - value->AppendAsTraceFormat(&json); - EXPECT_EQ( - "PREFIX{\"bool\":true,\"double\":0.0,\"int\":2014,\"string\":\"string\"}", - json); -} - -TEST(TraceEventArgumentTest, NoDotPathExpansion) { - std::unique_ptr value(new TracedValue()); - value->SetInteger("in.t", 2014); - value->SetDouble("doub.le", 0.0); - value->SetBoolean("bo.ol", true); - value->SetString("str.ing", "str.ing"); - std::string json; - value->AppendAsTraceFormat(&json); - EXPECT_EQ( - "{\"bo.ol\":true,\"doub.le\":0.0,\"in.t\":2014,\"str.ing\":\"str.ing\"}", - json); -} - -TEST(TraceEventArgumentTest, Hierarchy) { - std::unique_ptr value(new TracedValue()); - value->SetInteger("i0", 2014); - value->BeginDictionary("dict1"); - value->SetInteger("i1", 2014); - value->BeginDictionary("dict2"); - value->SetBoolean("b2", false); - value->EndDictionary(); - value->SetString("s1", "foo"); - value->EndDictionary(); - value->SetDouble("d0", 0.0); - value->SetBoolean("b0", true); - value->BeginArray("a1"); - value->AppendInteger(1); - value->AppendBoolean(true); - value->BeginDictionary(); - value->SetInteger("i2", 3); - value->EndDictionary(); - value->EndArray(); - value->SetString("s0", "foo"); - std::string json; - value->AppendAsTraceFormat(&json); - EXPECT_EQ( - "{\"a1\":[1,true,{\"i2\":3}],\"b0\":true,\"d0\":0.0,\"dict1\":{\"dict2\":" - "{\"b2\":false},\"i1\":2014,\"s1\":\"foo\"},\"i0\":2014,\"s0\":" - "\"foo\"}", - json); -} - -TEST(TraceEventArgumentTest, LongStrings) { - std::string kLongString = "supercalifragilisticexpialidocious"; - std::string kLongString2 = "0123456789012345678901234567890123456789"; - char kLongString3[4096]; - for (size_t i = 0; i < sizeof(kLongString3); ++i) - kLongString3[i] = 'a' + (i % 25); - kLongString3[sizeof(kLongString3) - 1] = '\0'; - - std::unique_ptr value(new TracedValue()); - value->SetString("a", "short"); - value->SetString("b", kLongString); - value->BeginArray("c"); - value->AppendString(kLongString2); - value->AppendString(""); - value->BeginDictionary(); - value->SetString("a", kLongString3); - value->EndDictionary(); - value->EndArray(); - - std::string json; - value->AppendAsTraceFormat(&json); - EXPECT_EQ("{\"a\":\"short\",\"b\":\"" + kLongString + "\",\"c\":[\"" + - kLongString2 + "\",\"\",{\"a\":\"" + kLongString3 + "\"}]}", - json); -} - -TEST(TraceEventArgumentTest, PassBaseValue) { - Value int_value(42); - Value bool_value(true); - Value double_value(42.0f); - - auto dict_value = WrapUnique(new DictionaryValue); - dict_value->SetBoolean("bool", true); - dict_value->SetInteger("int", 42); - dict_value->SetDouble("double", 42.0f); - dict_value->SetString("string", std::string("a") + "b"); - dict_value->SetString("string", std::string("a") + "b"); - - auto list_value = WrapUnique(new ListValue); - list_value->AppendBoolean(false); - list_value->AppendInteger(1); - list_value->AppendString("in_list"); - list_value->Append(std::move(dict_value)); - - std::unique_ptr value(new TracedValue()); - value->BeginDictionary("outer_dict"); - value->SetValue("inner_list", std::move(list_value)); - value->EndDictionary(); - - dict_value.reset(); - list_value.reset(); - - std::string json; - value->AppendAsTraceFormat(&json); - EXPECT_EQ( - "{\"outer_dict\":{\"inner_list\":[false,1,\"in_list\",{\"bool\":true," - "\"double\":42.0,\"int\":42,\"string\":\"ab\"}]}}", - json); -} - -TEST(TraceEventArgumentTest, PassTracedValue) { - auto dict_value = MakeUnique(); - dict_value->SetInteger("a", 1); - - auto nested_dict_value = MakeUnique(); - nested_dict_value->SetInteger("b", 2); - nested_dict_value->BeginArray("c"); - nested_dict_value->AppendString("foo"); - nested_dict_value->EndArray(); - - dict_value->SetValue("e", *nested_dict_value); - - // Check the merged result. - std::string json; - dict_value->AppendAsTraceFormat(&json); - EXPECT_EQ("{\"a\":1,\"e\":{\"b\":2,\"c\":[\"foo\"]}}", json); - - // Check that the passed nestd dict was left unouthced. - json = ""; - nested_dict_value->AppendAsTraceFormat(&json); - EXPECT_EQ("{\"b\":2,\"c\":[\"foo\"]}", json); - - // And that it is still usable. - nested_dict_value->SetInteger("f", 3); - nested_dict_value->BeginDictionary("g"); - nested_dict_value->EndDictionary(); - json = ""; - nested_dict_value->AppendAsTraceFormat(&json); - EXPECT_EQ("{\"b\":2,\"c\":[\"foo\"],\"f\":3,\"g\":{}}", json); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/trace_event_filter.cc b/base/trace_event/trace_event_filter.cc deleted file mode 100644 index 626529586477d1d1d16f7835c80a916e20b311e8..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_filter.cc +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2016 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. - -#include "base/trace_event/trace_event_filter.h" - -namespace base { -namespace trace_event { - -TraceEventFilter::TraceEventFilter() {} -TraceEventFilter::~TraceEventFilter() {} - -void TraceEventFilter::EndEvent(const char* category_name, - const char* event_name) const {} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/trace_event_filter.h b/base/trace_event/trace_event_filter.h deleted file mode 100644 index 48c6711432fdadc013b3b6ca5be668d42337e98e..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_filter.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2016 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 BASE_TRACE_EVENT_TRACE_EVENT_FILTER_H_ -#define BASE_TRACE_EVENT_TRACE_EVENT_FILTER_H_ - -#include - -#include "base/base_export.h" -#include "base/macros.h" - -namespace base { -namespace trace_event { - -class TraceEvent; - -// TraceEventFilter is like iptables for TRACE_EVENT macros. Filters can be -// enabled on a per-category basis, hence a single filter instance can serve -// more than a TraceCategory. There are two use cases for filters: -// 1. Snooping TRACE_EVENT macros without adding them to the TraceLog. This is -// possible by setting the ENABLED_FOR_FILTERING flag on a category w/o -// ENABLED_FOR_RECORDING (see TraceConfig for user-facing configuration). -// 2. Filtering TRACE_EVENT macros before they are added to the TraceLog. This -// requires both the ENABLED_FOR_FILTERING and ENABLED_FOR_RECORDING flags -// on the category. -// More importantly, filters must be thread-safe. The FilterTraceEvent and -// EndEvent methods can be called concurrently as trace macros are hit on -// different threads. -class BASE_EXPORT TraceEventFilter { - public: - TraceEventFilter(); - virtual ~TraceEventFilter(); - - // If the category is ENABLED_FOR_RECORDING, the event is added iff all the - // filters enabled for the category return true. false causes the event to be - // discarded. - virtual bool FilterTraceEvent(const TraceEvent& trace_event) const = 0; - - // Notifies the end of a duration event when the RAII macro goes out of scope. - virtual void EndEvent(const char* category_name, - const char* event_name) const; - - private: - DISALLOW_COPY_AND_ASSIGN(TraceEventFilter); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_EVENT_FILTER_H_ diff --git a/base/trace_event/trace_event_filter_test_utils.cc b/base/trace_event/trace_event_filter_test_utils.cc deleted file mode 100644 index 06548b049a2f1e69d212dbef2315e8d3fd8b5bea..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_filter_test_utils.cc +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2016 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. - -#include "base/trace_event/trace_event_filter_test_utils.h" - -#include "base/logging.h" - -namespace base { -namespace trace_event { - -namespace { -TestEventFilter::HitsCounter* g_hits_counter; -} // namespace; - -// static -const char TestEventFilter::kName[] = "testing_predicate"; -bool TestEventFilter::filter_return_value_; - -// static -std::unique_ptr TestEventFilter::Factory( - const std::string& predicate_name) { - std::unique_ptr res; - if (predicate_name == kName) - res.reset(new TestEventFilter()); - return res; -} - -TestEventFilter::TestEventFilter() {} -TestEventFilter::~TestEventFilter() {} - -bool TestEventFilter::FilterTraceEvent(const TraceEvent& trace_event) const { - if (g_hits_counter) - g_hits_counter->filter_trace_event_hit_count++; - return filter_return_value_; -} - -void TestEventFilter::EndEvent(const char* category_name, - const char* name) const { - if (g_hits_counter) - g_hits_counter->end_event_hit_count++; -} - -TestEventFilter::HitsCounter::HitsCounter() { - Reset(); - DCHECK(!g_hits_counter); - g_hits_counter = this; -} - -TestEventFilter::HitsCounter::~HitsCounter() { - DCHECK(g_hits_counter); - g_hits_counter = nullptr; -} - -void TestEventFilter::HitsCounter::Reset() { - filter_trace_event_hit_count = 0; - end_event_hit_count = 0; -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/trace_event_filter_test_utils.h b/base/trace_event/trace_event_filter_test_utils.h deleted file mode 100644 index 419068b221ace663d1e26ba80a074c1026c27f23..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_filter_test_utils.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2016 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 BASE_TRACE_EVENT_TRACE_EVENT_FILTER_TEST_UTILS_H_ -#define BASE_TRACE_EVENT_TRACE_EVENT_FILTER_TEST_UTILS_H_ - -#include -#include - -#include "base/macros.h" -#include "base/trace_event/trace_event_filter.h" - -namespace base { -namespace trace_event { - -class TestEventFilter : public TraceEventFilter { - public: - struct HitsCounter { - HitsCounter(); - ~HitsCounter(); - void Reset(); - size_t filter_trace_event_hit_count; - size_t end_event_hit_count; - }; - - static const char kName[]; - - // Factory method for TraceLog::SetFilterFactoryForTesting(). - static std::unique_ptr Factory( - const std::string& predicate_name); - - TestEventFilter(); - ~TestEventFilter() override; - - // TraceEventFilter implementation. - bool FilterTraceEvent(const TraceEvent& trace_event) const override; - void EndEvent(const char* category_name, const char* name) const override; - - static void set_filter_return_value(bool value) { - filter_return_value_ = value; - } - - private: - static bool filter_return_value_; - - DISALLOW_COPY_AND_ASSIGN(TestEventFilter); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_EVENT_FILTER_TEST_UTILS_H_ diff --git a/base/trace_event/trace_event_impl.cc b/base/trace_event/trace_event_impl.cc deleted file mode 100644 index cb23eb474c54e605f9f83f01367b896d1d445a5f..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_impl.cc +++ /dev/null @@ -1,490 +0,0 @@ -// Copyright (c) 2012 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. - -#include "base/trace_event/trace_event_impl.h" - -#include - -#include "base/format_macros.h" -#include "base/json/string_escape.h" -#include "base/memory/ptr_util.h" -#include "base/process/process_handle.h" -#include "base/stl_util.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_util.h" -#include "base/strings/stringprintf.h" -#include "base/strings/utf_string_conversions.h" -#include "base/trace_event/trace_event.h" -#include "base/trace_event/trace_event_argument.h" -#include "base/trace_event/trace_log.h" - -namespace base { -namespace trace_event { - -namespace { - -size_t GetAllocLength(const char* str) { return str ? strlen(str) + 1 : 0; } - -// Copies |*member| into |*buffer|, sets |*member| to point to this new -// location, and then advances |*buffer| by the amount written. -void CopyTraceEventParameter(char** buffer, - const char** member, - const char* end) { - if (*member) { - size_t written = strlcpy(*buffer, *member, end - *buffer) + 1; - DCHECK_LE(static_cast(written), end - *buffer); - *member = *buffer; - *buffer += written; - } -} - -} // namespace - -TraceEvent::TraceEvent() - : duration_(TimeDelta::FromInternalValue(-1)), - scope_(trace_event_internal::kGlobalScope), - id_(0u), - category_group_enabled_(NULL), - name_(NULL), - thread_id_(0), - flags_(0), - phase_(TRACE_EVENT_PHASE_BEGIN) { - for (int i = 0; i < kTraceMaxNumArgs; ++i) - arg_names_[i] = NULL; - memset(arg_values_, 0, sizeof(arg_values_)); -} - -TraceEvent::~TraceEvent() { -} - -void TraceEvent::MoveFrom(std::unique_ptr other) { - timestamp_ = other->timestamp_; - thread_timestamp_ = other->thread_timestamp_; - duration_ = other->duration_; - scope_ = other->scope_; - id_ = other->id_; - category_group_enabled_ = other->category_group_enabled_; - name_ = other->name_; - if (other->flags_ & TRACE_EVENT_FLAG_HAS_PROCESS_ID) - process_id_ = other->process_id_; - else - thread_id_ = other->thread_id_; - phase_ = other->phase_; - flags_ = other->flags_; - parameter_copy_storage_ = std::move(other->parameter_copy_storage_); - - for (int i = 0; i < kTraceMaxNumArgs; ++i) { - arg_names_[i] = other->arg_names_[i]; - arg_types_[i] = other->arg_types_[i]; - arg_values_[i] = other->arg_values_[i]; - convertable_values_[i] = std::move(other->convertable_values_[i]); - } -} - -void TraceEvent::Initialize( - int thread_id, - TimeTicks timestamp, - ThreadTicks thread_timestamp, - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - unsigned long long bind_id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags) { - timestamp_ = timestamp; - thread_timestamp_ = thread_timestamp; - duration_ = TimeDelta::FromInternalValue(-1); - scope_ = scope; - id_ = id; - category_group_enabled_ = category_group_enabled; - name_ = name; - thread_id_ = thread_id; - phase_ = phase; - flags_ = flags; - bind_id_ = bind_id; - - // Clamp num_args since it may have been set by a third_party library. - num_args = (num_args > kTraceMaxNumArgs) ? kTraceMaxNumArgs : num_args; - int i = 0; - for (; i < num_args; ++i) { - arg_names_[i] = arg_names[i]; - arg_types_[i] = arg_types[i]; - - if (arg_types[i] == TRACE_VALUE_TYPE_CONVERTABLE) { - convertable_values_[i] = std::move(convertable_values[i]); - } else { - arg_values_[i].as_uint = arg_values[i]; - convertable_values_[i].reset(); - } - } - for (; i < kTraceMaxNumArgs; ++i) { - arg_names_[i] = NULL; - arg_values_[i].as_uint = 0u; - convertable_values_[i].reset(); - arg_types_[i] = TRACE_VALUE_TYPE_UINT; - } - - bool copy = !!(flags & TRACE_EVENT_FLAG_COPY); - size_t alloc_size = 0; - if (copy) { - alloc_size += GetAllocLength(name) + GetAllocLength(scope); - for (i = 0; i < num_args; ++i) { - alloc_size += GetAllocLength(arg_names_[i]); - if (arg_types_[i] == TRACE_VALUE_TYPE_STRING) - arg_types_[i] = TRACE_VALUE_TYPE_COPY_STRING; - } - } - - bool arg_is_copy[kTraceMaxNumArgs]; - for (i = 0; i < num_args; ++i) { - // No copying of convertable types, we retain ownership. - if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) - continue; - - // We only take a copy of arg_vals if they are of type COPY_STRING. - arg_is_copy[i] = (arg_types_[i] == TRACE_VALUE_TYPE_COPY_STRING); - if (arg_is_copy[i]) - alloc_size += GetAllocLength(arg_values_[i].as_string); - } - - if (alloc_size) { - parameter_copy_storage_.reset(new std::string); - parameter_copy_storage_->resize(alloc_size); - char* ptr = string_as_array(parameter_copy_storage_.get()); - const char* end = ptr + alloc_size; - if (copy) { - CopyTraceEventParameter(&ptr, &name_, end); - CopyTraceEventParameter(&ptr, &scope_, end); - for (i = 0; i < num_args; ++i) { - CopyTraceEventParameter(&ptr, &arg_names_[i], end); - } - } - for (i = 0; i < num_args; ++i) { - if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) - continue; - if (arg_is_copy[i]) - CopyTraceEventParameter(&ptr, &arg_values_[i].as_string, end); - } - DCHECK_EQ(end, ptr) << "Overrun by " << ptr - end; - } -} - -void TraceEvent::Reset() { - // Only reset fields that won't be initialized in Initialize(), or that may - // hold references to other objects. - duration_ = TimeDelta::FromInternalValue(-1); - parameter_copy_storage_.reset(); - for (int i = 0; i < kTraceMaxNumArgs; ++i) - convertable_values_[i].reset(); -} - -void TraceEvent::UpdateDuration(const TimeTicks& now, - const ThreadTicks& thread_now) { - DCHECK_EQ(duration_.ToInternalValue(), -1); - duration_ = now - timestamp_; - - // |thread_timestamp_| can be empty if the thread ticks clock wasn't - // initialized when it was recorded. - if (thread_timestamp_ != ThreadTicks()) - thread_duration_ = thread_now - thread_timestamp_; -} - -void TraceEvent::EstimateTraceMemoryOverhead( - TraceEventMemoryOverhead* overhead) { - overhead->Add("TraceEvent", sizeof(*this)); - - if (parameter_copy_storage_) - overhead->AddString(*parameter_copy_storage_); - - for (size_t i = 0; i < kTraceMaxNumArgs; ++i) { - if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) - convertable_values_[i]->EstimateTraceMemoryOverhead(overhead); - } -} - -// static -void TraceEvent::AppendValueAsJSON(unsigned char type, - TraceEvent::TraceValue value, - std::string* out) { - switch (type) { - case TRACE_VALUE_TYPE_BOOL: - *out += value.as_bool ? "true" : "false"; - break; - case TRACE_VALUE_TYPE_UINT: - StringAppendF(out, "%" PRIu64, static_cast(value.as_uint)); - break; - case TRACE_VALUE_TYPE_INT: - StringAppendF(out, "%" PRId64, static_cast(value.as_int)); - break; - case TRACE_VALUE_TYPE_DOUBLE: { - // FIXME: base/json/json_writer.cc is using the same code, - // should be made into a common method. - std::string real; - double val = value.as_double; - if (std::isfinite(val)) { - real = DoubleToString(val); - // Ensure that the number has a .0 if there's no decimal or 'e'. This - // makes sure that when we read the JSON back, it's interpreted as a - // real rather than an int. - if (real.find('.') == std::string::npos && - real.find('e') == std::string::npos && - real.find('E') == std::string::npos) { - real.append(".0"); - } - // The JSON spec requires that non-integer values in the range (-1,1) - // have a zero before the decimal point - ".52" is not valid, "0.52" is. - if (real[0] == '.') { - real.insert(0, "0"); - } else if (real.length() > 1 && real[0] == '-' && real[1] == '.') { - // "-.1" bad "-0.1" good - real.insert(1, "0"); - } - } else if (std::isnan(val)){ - // The JSON spec doesn't allow NaN and Infinity (since these are - // objects in EcmaScript). Use strings instead. - real = "\"NaN\""; - } else if (val < 0) { - real = "\"-Infinity\""; - } else { - real = "\"Infinity\""; - } - StringAppendF(out, "%s", real.c_str()); - break; - } - case TRACE_VALUE_TYPE_POINTER: - // JSON only supports double and int numbers. - // So as not to lose bits from a 64-bit pointer, output as a hex string. - StringAppendF( - out, "\"0x%" PRIx64 "\"", - static_cast(reinterpret_cast(value.as_pointer))); - break; - case TRACE_VALUE_TYPE_STRING: - case TRACE_VALUE_TYPE_COPY_STRING: - EscapeJSONString(value.as_string ? value.as_string : "NULL", true, out); - break; - default: - NOTREACHED() << "Don't know how to print this value"; - break; - } -} - -void TraceEvent::AppendAsJSON( - std::string* out, - const ArgumentFilterPredicate& argument_filter_predicate) const { - int64_t time_int64 = timestamp_.ToInternalValue(); - int process_id; - int thread_id; - if ((flags_ & TRACE_EVENT_FLAG_HAS_PROCESS_ID) && - process_id_ != kNullProcessId) { - process_id = process_id_; - thread_id = -1; - } else { - process_id = TraceLog::GetInstance()->process_id(); - thread_id = thread_id_; - } - const char* category_group_name = - TraceLog::GetCategoryGroupName(category_group_enabled_); - - // Category group checked at category creation time. - DCHECK(!strchr(name_, '"')); - StringAppendF(out, "{\"pid\":%i,\"tid\":%i,\"ts\":%" PRId64 - ",\"ph\":\"%c\",\"cat\":\"%s\",\"name\":", - process_id, thread_id, time_int64, phase_, category_group_name); - EscapeJSONString(name_, true, out); - *out += ",\"args\":"; - - // Output argument names and values, stop at first NULL argument name. - // TODO(oysteine): The dual predicates here is a bit ugly; if the filtering - // capabilities need to grow even more precise we should rethink this - // approach - ArgumentNameFilterPredicate argument_name_filter_predicate; - bool strip_args = - arg_names_[0] && !argument_filter_predicate.is_null() && - !argument_filter_predicate.Run(category_group_name, name_, - &argument_name_filter_predicate); - - if (strip_args) { - *out += "\"__stripped__\""; - } else { - *out += "{"; - - for (int i = 0; i < kTraceMaxNumArgs && arg_names_[i]; ++i) { - if (i > 0) - *out += ","; - *out += "\""; - *out += arg_names_[i]; - *out += "\":"; - - if (argument_name_filter_predicate.is_null() || - argument_name_filter_predicate.Run(arg_names_[i])) { - if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) - convertable_values_[i]->AppendAsTraceFormat(out); - else - AppendValueAsJSON(arg_types_[i], arg_values_[i], out); - } else { - *out += "\"__stripped__\""; - } - } - - *out += "}"; - } - - if (phase_ == TRACE_EVENT_PHASE_COMPLETE) { - int64_t duration = duration_.ToInternalValue(); - if (duration != -1) - StringAppendF(out, ",\"dur\":%" PRId64, duration); - if (!thread_timestamp_.is_null()) { - int64_t thread_duration = thread_duration_.ToInternalValue(); - if (thread_duration != -1) - StringAppendF(out, ",\"tdur\":%" PRId64, thread_duration); - } - } - - // Output tts if thread_timestamp is valid. - if (!thread_timestamp_.is_null()) { - int64_t thread_time_int64 = thread_timestamp_.ToInternalValue(); - StringAppendF(out, ",\"tts\":%" PRId64, thread_time_int64); - } - - // Output async tts marker field if flag is set. - if (flags_ & TRACE_EVENT_FLAG_ASYNC_TTS) { - StringAppendF(out, ", \"use_async_tts\":1"); - } - - // If id_ is set, print it out as a hex string so we don't loose any - // bits (it might be a 64-bit pointer). - unsigned int id_flags_ = flags_ & (TRACE_EVENT_FLAG_HAS_ID | - TRACE_EVENT_FLAG_HAS_LOCAL_ID | - TRACE_EVENT_FLAG_HAS_GLOBAL_ID); - if (id_flags_) { - if (scope_ != trace_event_internal::kGlobalScope) - StringAppendF(out, ",\"scope\":\"%s\"", scope_); - - switch (id_flags_) { - case TRACE_EVENT_FLAG_HAS_ID: - StringAppendF(out, ",\"id\":\"0x%" PRIx64 "\"", - static_cast(id_)); - break; - - case TRACE_EVENT_FLAG_HAS_LOCAL_ID: - StringAppendF(out, ",\"id2\":{\"local\":\"0x%" PRIx64 "\"}", - static_cast(id_)); - break; - - case TRACE_EVENT_FLAG_HAS_GLOBAL_ID: - StringAppendF(out, ",\"id2\":{\"global\":\"0x%" PRIx64 "\"}", - static_cast(id_)); - break; - - default: - NOTREACHED() << "More than one of the ID flags are set"; - break; - } - } - - if (flags_ & TRACE_EVENT_FLAG_BIND_TO_ENCLOSING) - StringAppendF(out, ",\"bp\":\"e\""); - - if ((flags_ & TRACE_EVENT_FLAG_FLOW_OUT) || - (flags_ & TRACE_EVENT_FLAG_FLOW_IN)) { - StringAppendF(out, ",\"bind_id\":\"0x%" PRIx64 "\"", - static_cast(bind_id_)); - } - if (flags_ & TRACE_EVENT_FLAG_FLOW_IN) - StringAppendF(out, ",\"flow_in\":true"); - if (flags_ & TRACE_EVENT_FLAG_FLOW_OUT) - StringAppendF(out, ",\"flow_out\":true"); - - // Instant events also output their scope. - if (phase_ == TRACE_EVENT_PHASE_INSTANT) { - char scope = '?'; - switch (flags_ & TRACE_EVENT_FLAG_SCOPE_MASK) { - case TRACE_EVENT_SCOPE_GLOBAL: - scope = TRACE_EVENT_SCOPE_NAME_GLOBAL; - break; - - case TRACE_EVENT_SCOPE_PROCESS: - scope = TRACE_EVENT_SCOPE_NAME_PROCESS; - break; - - case TRACE_EVENT_SCOPE_THREAD: - scope = TRACE_EVENT_SCOPE_NAME_THREAD; - break; - } - StringAppendF(out, ",\"s\":\"%c\"", scope); - } - - *out += "}"; -} - -void TraceEvent::AppendPrettyPrinted(std::ostringstream* out) const { - *out << name_ << "["; - *out << TraceLog::GetCategoryGroupName(category_group_enabled_); - *out << "]"; - if (arg_names_[0]) { - *out << ", {"; - for (int i = 0; i < kTraceMaxNumArgs && arg_names_[i]; ++i) { - if (i > 0) - *out << ", "; - *out << arg_names_[i] << ":"; - std::string value_as_text; - - if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) - convertable_values_[i]->AppendAsTraceFormat(&value_as_text); - else - AppendValueAsJSON(arg_types_[i], arg_values_[i], &value_as_text); - - *out << value_as_text; - } - *out << "}"; - } -} - -} // namespace trace_event -} // namespace base - -namespace trace_event_internal { - -std::unique_ptr -TraceID::AsConvertableToTraceFormat() const { - auto value = base::MakeUnique(); - - if (scope_ != kGlobalScope) - value->SetString("scope", scope_); - - const char* id_field_name = "id"; - if (id_flags_ == TRACE_EVENT_FLAG_HAS_GLOBAL_ID) { - id_field_name = "global"; - value->BeginDictionary("id2"); - } else if (id_flags_ == TRACE_EVENT_FLAG_HAS_LOCAL_ID) { - id_field_name = "local"; - value->BeginDictionary("id2"); - } else if (id_flags_ != TRACE_EVENT_FLAG_HAS_ID) { - NOTREACHED() << "Unrecognized ID flag"; - } - - if (has_prefix_) { - value->SetString(id_field_name, - base::StringPrintf("0x%" PRIx64 "/0x%" PRIx64, - static_cast(prefix_), - static_cast(raw_id_))); - } else { - value->SetString( - id_field_name, - base::StringPrintf("0x%" PRIx64, static_cast(raw_id_))); - } - - if (id_flags_ != TRACE_EVENT_FLAG_HAS_ID) - value->EndDictionary(); - - return std::move(value); -} - -} // namespace trace_event_internal diff --git a/base/trace_event/trace_event_impl.h b/base/trace_event/trace_event_impl.h deleted file mode 100644 index 5eef702fb90a6dc95e2c329e8b8377f62f344ab3..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_impl.h +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) 2012 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 BASE_TRACE_EVENT_TRACE_EVENT_IMPL_H_ -#define BASE_TRACE_EVENT_TRACE_EVENT_IMPL_H_ - -#include - -#include -#include -#include -#include - -#include "base/atomicops.h" -#include "base/base_export.h" -#include "base/callback.h" -#include "base/containers/hash_tables.h" -#include "base/macros.h" -#include "base/observer_list.h" -#include "base/single_thread_task_runner.h" -#include "base/strings/string_util.h" -#include "base/synchronization/condition_variable.h" -#include "base/synchronization/lock.h" -#include "base/threading/thread_local.h" -#include "base/trace_event/trace_event_memory_overhead.h" -#include "build/build_config.h" - -namespace base { -namespace trace_event { - -typedef base::Callback ArgumentNameFilterPredicate; - -typedef base::Callback - ArgumentFilterPredicate; - -// For any argument of type TRACE_VALUE_TYPE_CONVERTABLE the provided -// class must implement this interface. -class BASE_EXPORT ConvertableToTraceFormat { - public: - ConvertableToTraceFormat() {} - virtual ~ConvertableToTraceFormat() {} - - // Append the class info to the provided |out| string. The appended - // data must be a valid JSON object. Strings must be properly quoted, and - // escaped. There is no processing applied to the content after it is - // appended. - virtual void AppendAsTraceFormat(std::string* out) const = 0; - - virtual void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead); - - std::string ToString() const { - std::string result; - AppendAsTraceFormat(&result); - return result; - } - - private: - DISALLOW_COPY_AND_ASSIGN(ConvertableToTraceFormat); -}; - -const int kTraceMaxNumArgs = 2; - -struct TraceEventHandle { - uint32_t chunk_seq; - // These numbers of bits must be kept consistent with - // TraceBufferChunk::kMaxTrunkIndex and - // TraceBufferChunk::kTraceBufferChunkSize (in trace_buffer.h). - unsigned chunk_index : 26; - unsigned event_index : 6; -}; - -class BASE_EXPORT TraceEvent { - public: - union TraceValue { - bool as_bool; - unsigned long long as_uint; - long long as_int; - double as_double; - const void* as_pointer; - const char* as_string; - }; - - TraceEvent(); - ~TraceEvent(); - - void MoveFrom(std::unique_ptr other); - - void Initialize(int thread_id, - TimeTicks timestamp, - ThreadTicks thread_timestamp, - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - unsigned long long bind_id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags); - - void Reset(); - - void UpdateDuration(const TimeTicks& now, const ThreadTicks& thread_now); - - void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead); - - // Serialize event data to JSON - void AppendAsJSON( - std::string* out, - const ArgumentFilterPredicate& argument_filter_predicate) const; - void AppendPrettyPrinted(std::ostringstream* out) const; - - static void AppendValueAsJSON(unsigned char type, - TraceValue value, - std::string* out); - - TimeTicks timestamp() const { return timestamp_; } - ThreadTicks thread_timestamp() const { return thread_timestamp_; } - char phase() const { return phase_; } - int thread_id() const { return thread_id_; } - TimeDelta duration() const { return duration_; } - TimeDelta thread_duration() const { return thread_duration_; } - const char* scope() const { return scope_; } - unsigned long long id() const { return id_; } - unsigned int flags() const { return flags_; } - - // Exposed for unittesting: - - const std::string* parameter_copy_storage() const { - return parameter_copy_storage_.get(); - } - - const unsigned char* category_group_enabled() const { - return category_group_enabled_; - } - - const char* name() const { return name_; } - -#if defined(OS_ANDROID) - void SendToATrace(); -#endif - - private: - // Note: these are ordered by size (largest first) for optimal packing. - TimeTicks timestamp_; - ThreadTicks thread_timestamp_; - TimeDelta duration_; - TimeDelta thread_duration_; - // scope_ and id_ can be used to store phase-specific data. - const char* scope_; - unsigned long long id_; - TraceValue arg_values_[kTraceMaxNumArgs]; - const char* arg_names_[kTraceMaxNumArgs]; - std::unique_ptr - convertable_values_[kTraceMaxNumArgs]; - const unsigned char* category_group_enabled_; - const char* name_; - std::unique_ptr parameter_copy_storage_; - // Depending on TRACE_EVENT_FLAG_HAS_PROCESS_ID the event will have either: - // tid: thread_id_, pid: current_process_id (default case). - // tid: -1, pid: process_id_ (when flags_ & TRACE_EVENT_FLAG_HAS_PROCESS_ID). - union { - int thread_id_; - int process_id_; - }; - unsigned int flags_; - unsigned long long bind_id_; - unsigned char arg_types_[kTraceMaxNumArgs]; - char phase_; - - DISALLOW_COPY_AND_ASSIGN(TraceEvent); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_EVENT_IMPL_H_ diff --git a/base/trace_event/trace_event_memory_overhead.cc b/base/trace_event/trace_event_memory_overhead.cc deleted file mode 100644 index 8d56e1d80e8d05a7d155a2a91e424ff8a7723a25..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_memory_overhead.cc +++ /dev/null @@ -1,156 +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. - -#include "base/trace_event/trace_event_memory_overhead.h" - -#include - -#include "base/bits.h" -#include "base/memory/ref_counted_memory.h" -#include "base/strings/stringprintf.h" -#include "base/trace_event/memory_allocator_dump.h" -#include "base/trace_event/process_memory_dump.h" -#include "base/values.h" - -namespace base { -namespace trace_event { - -TraceEventMemoryOverhead::TraceEventMemoryOverhead() { -} - -TraceEventMemoryOverhead::~TraceEventMemoryOverhead() { -} - -void TraceEventMemoryOverhead::AddOrCreateInternal( - const char* object_type, - size_t count, - size_t allocated_size_in_bytes, - size_t resident_size_in_bytes) { - auto it = allocated_objects_.find(object_type); - if (it == allocated_objects_.end()) { - allocated_objects_.insert(std::make_pair( - object_type, - ObjectCountAndSize( - {count, allocated_size_in_bytes, resident_size_in_bytes}))); - return; - } - it->second.count += count; - it->second.allocated_size_in_bytes += allocated_size_in_bytes; - it->second.resident_size_in_bytes += resident_size_in_bytes; -} - -void TraceEventMemoryOverhead::Add(const char* object_type, - size_t allocated_size_in_bytes) { - Add(object_type, allocated_size_in_bytes, allocated_size_in_bytes); -} - -void TraceEventMemoryOverhead::Add(const char* object_type, - size_t allocated_size_in_bytes, - size_t resident_size_in_bytes) { - AddOrCreateInternal(object_type, 1, allocated_size_in_bytes, - resident_size_in_bytes); -} - -void TraceEventMemoryOverhead::AddString(const std::string& str) { - // The number below are empirical and mainly based on profiling of real-world - // std::string implementations: - // - even short string end up malloc()-inc at least 32 bytes. - // - longer strings seem to malloc() multiples of 16 bytes. - const size_t capacity = bits::Align(str.capacity(), 16); - Add("std::string", sizeof(std::string) + std::max(capacity, 32u)); -} - -void TraceEventMemoryOverhead::AddRefCountedString( - const RefCountedString& str) { - Add("RefCountedString", sizeof(RefCountedString)); - AddString(str.data()); -} - -void TraceEventMemoryOverhead::AddValue(const Value& value) { - switch (value.GetType()) { - case Value::Type::NONE: - case Value::Type::BOOLEAN: - case Value::Type::INTEGER: - case Value::Type::DOUBLE: - Add("FundamentalValue", sizeof(Value)); - break; - - case Value::Type::STRING: { - const Value* string_value = nullptr; - value.GetAsString(&string_value); - Add("StringValue", sizeof(Value)); - AddString(string_value->GetString()); - } break; - - case Value::Type::BINARY: { - const BinaryValue* binary_value = nullptr; - value.GetAsBinary(&binary_value); - Add("BinaryValue", sizeof(BinaryValue) + binary_value->GetSize()); - } break; - - case Value::Type::DICTIONARY: { - const DictionaryValue* dictionary_value = nullptr; - value.GetAsDictionary(&dictionary_value); - Add("DictionaryValue", sizeof(DictionaryValue)); - for (DictionaryValue::Iterator it(*dictionary_value); !it.IsAtEnd(); - it.Advance()) { - AddString(it.key()); - AddValue(it.value()); - } - } break; - - case Value::Type::LIST: { - const ListValue* list_value = nullptr; - value.GetAsList(&list_value); - Add("ListValue", sizeof(ListValue)); - for (const auto& v : *list_value) - AddValue(*v); - } break; - - default: - NOTREACHED(); - } -} - -void TraceEventMemoryOverhead::AddSelf() { - size_t estimated_size = sizeof(*this); - // If the SmallMap did overflow its static capacity, its elements will be - // allocated on the heap and have to be accounted separately. - if (allocated_objects_.UsingFullMap()) - estimated_size += sizeof(map_type::value_type) * allocated_objects_.size(); - Add("TraceEventMemoryOverhead", estimated_size); -} - -size_t TraceEventMemoryOverhead::GetCount(const char* object_type) const { - const auto& it = allocated_objects_.find(object_type); - if (it == allocated_objects_.end()) - return 0u; - return it->second.count; -} - -void TraceEventMemoryOverhead::Update(const TraceEventMemoryOverhead& other) { - for (const auto& it : other.allocated_objects_) { - AddOrCreateInternal(it.first, it.second.count, - it.second.allocated_size_in_bytes, - it.second.resident_size_in_bytes); - } -} - -void TraceEventMemoryOverhead::DumpInto(const char* base_name, - ProcessMemoryDump* pmd) const { - for (const auto& it : allocated_objects_) { - std::string dump_name = StringPrintf("%s/%s", base_name, it.first); - MemoryAllocatorDump* mad = pmd->CreateAllocatorDump(dump_name); - mad->AddScalar(MemoryAllocatorDump::kNameSize, - MemoryAllocatorDump::kUnitsBytes, - it.second.allocated_size_in_bytes); - mad->AddScalar("resident_size", MemoryAllocatorDump::kUnitsBytes, - it.second.resident_size_in_bytes); - mad->AddScalar(MemoryAllocatorDump::kNameObjectCount, - MemoryAllocatorDump::kUnitsObjects, it.second.count); - } -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/trace_event_memory_overhead.h b/base/trace_event/trace_event_memory_overhead.h deleted file mode 100644 index a69c93fed200de667996c045ba8411db05a9c17e..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_memory_overhead.h +++ /dev/null @@ -1,77 +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. - -#ifndef BASE_TRACE_EVENT_TRACE_EVENT_MEMORY_OVERHEAD_H_ -#define BASE_TRACE_EVENT_TRACE_EVENT_MEMORY_OVERHEAD_H_ - -#include - -#include "base/base_export.h" -#include "base/containers/hash_tables.h" -#include "base/containers/small_map.h" -#include "base/macros.h" - -namespace base { - -class RefCountedString; -class Value; - -namespace trace_event { - -class ProcessMemoryDump; - -// Used to estimate the memory overhead of the tracing infrastructure. -class BASE_EXPORT TraceEventMemoryOverhead { - public: - TraceEventMemoryOverhead(); - ~TraceEventMemoryOverhead(); - - // Use this method to account the overhead of an object for which an estimate - // is known for both the allocated and resident memory. - void Add(const char* object_type, - size_t allocated_size_in_bytes, - size_t resident_size_in_bytes); - - // Similar to Add() above, but assumes that - // |resident_size_in_bytes| == |allocated_size_in_bytes|. - void Add(const char* object_type, size_t allocated_size_in_bytes); - - // Specialized profiling functions for commonly used object types. - void AddString(const std::string& str); - void AddValue(const Value& value); - void AddRefCountedString(const RefCountedString& str); - - // Call this after all the Add* methods above to account the memory used by - // this TraceEventMemoryOverhead instance itself. - void AddSelf(); - - // Retrieves the count, that is, the count of Add*(|object_type|, ...) calls. - size_t GetCount(const char* object_type) const; - - // Adds up and merges all the values from |other| to this instance. - void Update(const TraceEventMemoryOverhead& other); - - void DumpInto(const char* base_name, ProcessMemoryDump* pmd) const; - - private: - struct ObjectCountAndSize { - size_t count; - size_t allocated_size_in_bytes; - size_t resident_size_in_bytes; - }; - using map_type = SmallMap, 16>; - map_type allocated_objects_; - - void AddOrCreateInternal(const char* object_type, - size_t count, - size_t allocated_size_in_bytes, - size_t resident_size_in_bytes); - - DISALLOW_COPY_AND_ASSIGN(TraceEventMemoryOverhead); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_EVENT_MEMORY_OVERHEAD_H_ diff --git a/base/trace_event/trace_event_synthetic_delay.cc b/base/trace_event/trace_event_synthetic_delay.cc deleted file mode 100644 index cfae7435e9ba9ff30b98ca268520bdaf8aab681b..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_synthetic_delay.cc +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2014 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. - -#include "base/macros.h" -#include "base/memory/singleton.h" -#include "base/third_party/dynamic_annotations/dynamic_annotations.h" -#include "base/trace_event/trace_event_synthetic_delay.h" - -namespace { -const int kMaxSyntheticDelays = 32; -} // namespace - -namespace base { -namespace trace_event { - -TraceEventSyntheticDelayClock::TraceEventSyntheticDelayClock() {} -TraceEventSyntheticDelayClock::~TraceEventSyntheticDelayClock() {} - -class TraceEventSyntheticDelayRegistry : public TraceEventSyntheticDelayClock { - public: - static TraceEventSyntheticDelayRegistry* GetInstance(); - - TraceEventSyntheticDelay* GetOrCreateDelay(const char* name); - void ResetAllDelays(); - - // TraceEventSyntheticDelayClock implementation. - TimeTicks Now() override; - - private: - TraceEventSyntheticDelayRegistry(); - - friend struct DefaultSingletonTraits; - - Lock lock_; - TraceEventSyntheticDelay delays_[kMaxSyntheticDelays]; - TraceEventSyntheticDelay dummy_delay_; - subtle::Atomic32 delay_count_; - - DISALLOW_COPY_AND_ASSIGN(TraceEventSyntheticDelayRegistry); -}; - -TraceEventSyntheticDelay::TraceEventSyntheticDelay() - : mode_(STATIC), begin_count_(0), trigger_count_(0), clock_(NULL) {} - -TraceEventSyntheticDelay::~TraceEventSyntheticDelay() {} - -TraceEventSyntheticDelay* TraceEventSyntheticDelay::Lookup( - const std::string& name) { - return TraceEventSyntheticDelayRegistry::GetInstance()->GetOrCreateDelay( - name.c_str()); -} - -void TraceEventSyntheticDelay::Initialize( - const std::string& name, - TraceEventSyntheticDelayClock* clock) { - name_ = name; - clock_ = clock; -} - -void TraceEventSyntheticDelay::SetTargetDuration(TimeDelta target_duration) { - AutoLock lock(lock_); - target_duration_ = target_duration; - trigger_count_ = 0; - begin_count_ = 0; -} - -void TraceEventSyntheticDelay::SetMode(Mode mode) { - AutoLock lock(lock_); - mode_ = mode; -} - -void TraceEventSyntheticDelay::SetClock(TraceEventSyntheticDelayClock* clock) { - AutoLock lock(lock_); - clock_ = clock; -} - -void TraceEventSyntheticDelay::Begin() { - // Note that we check for a non-zero target duration without locking to keep - // things quick for the common case when delays are disabled. Since the delay - // calculation is done with a lock held, it will always be correct. The only - // downside of this is that we may fail to apply some delays when the target - // duration changes. - ANNOTATE_BENIGN_RACE(&target_duration_, "Synthetic delay duration"); - if (!target_duration_.ToInternalValue()) - return; - - TimeTicks start_time = clock_->Now(); - { - AutoLock lock(lock_); - if (++begin_count_ != 1) - return; - end_time_ = CalculateEndTimeLocked(start_time); - } -} - -void TraceEventSyntheticDelay::BeginParallel(TimeTicks* out_end_time) { - // See note in Begin(). - ANNOTATE_BENIGN_RACE(&target_duration_, "Synthetic delay duration"); - if (!target_duration_.ToInternalValue()) { - *out_end_time = TimeTicks(); - return; - } - - TimeTicks start_time = clock_->Now(); - { - AutoLock lock(lock_); - *out_end_time = CalculateEndTimeLocked(start_time); - } -} - -void TraceEventSyntheticDelay::End() { - // See note in Begin(). - ANNOTATE_BENIGN_RACE(&target_duration_, "Synthetic delay duration"); - if (!target_duration_.ToInternalValue()) - return; - - TimeTicks end_time; - { - AutoLock lock(lock_); - if (!begin_count_ || --begin_count_ != 0) - return; - end_time = end_time_; - } - if (!end_time.is_null()) - ApplyDelay(end_time); -} - -void TraceEventSyntheticDelay::EndParallel(TimeTicks end_time) { - if (!end_time.is_null()) - ApplyDelay(end_time); -} - -TimeTicks TraceEventSyntheticDelay::CalculateEndTimeLocked( - TimeTicks start_time) { - if (mode_ == ONE_SHOT && trigger_count_++) - return TimeTicks(); - else if (mode_ == ALTERNATING && trigger_count_++ % 2) - return TimeTicks(); - return start_time + target_duration_; -} - -void TraceEventSyntheticDelay::ApplyDelay(TimeTicks end_time) { - TRACE_EVENT0("synthetic_delay", name_.c_str()); - while (clock_->Now() < end_time) { - // Busy loop. - } -} - -TraceEventSyntheticDelayRegistry* -TraceEventSyntheticDelayRegistry::GetInstance() { - return Singleton< - TraceEventSyntheticDelayRegistry, - LeakySingletonTraits >::get(); -} - -TraceEventSyntheticDelayRegistry::TraceEventSyntheticDelayRegistry() - : delay_count_(0) {} - -TraceEventSyntheticDelay* TraceEventSyntheticDelayRegistry::GetOrCreateDelay( - const char* name) { - // Try to find an existing delay first without locking to make the common case - // fast. - int delay_count = subtle::Acquire_Load(&delay_count_); - for (int i = 0; i < delay_count; ++i) { - if (!strcmp(name, delays_[i].name_.c_str())) - return &delays_[i]; - } - - AutoLock lock(lock_); - delay_count = subtle::Acquire_Load(&delay_count_); - for (int i = 0; i < delay_count; ++i) { - if (!strcmp(name, delays_[i].name_.c_str())) - return &delays_[i]; - } - - DCHECK(delay_count < kMaxSyntheticDelays) - << "must increase kMaxSyntheticDelays"; - if (delay_count >= kMaxSyntheticDelays) - return &dummy_delay_; - - delays_[delay_count].Initialize(std::string(name), this); - subtle::Release_Store(&delay_count_, delay_count + 1); - return &delays_[delay_count]; -} - -TimeTicks TraceEventSyntheticDelayRegistry::Now() { - return TimeTicks::Now(); -} - -void TraceEventSyntheticDelayRegistry::ResetAllDelays() { - AutoLock lock(lock_); - int delay_count = subtle::Acquire_Load(&delay_count_); - for (int i = 0; i < delay_count; ++i) { - delays_[i].SetTargetDuration(TimeDelta()); - delays_[i].SetClock(this); - } -} - -void ResetTraceEventSyntheticDelays() { - TraceEventSyntheticDelayRegistry::GetInstance()->ResetAllDelays(); -} - -} // namespace trace_event -} // namespace base - -namespace trace_event_internal { - -ScopedSyntheticDelay::ScopedSyntheticDelay(const char* name, - base::subtle::AtomicWord* impl_ptr) - : delay_impl_(GetOrCreateDelay(name, impl_ptr)) { - delay_impl_->BeginParallel(&end_time_); -} - -ScopedSyntheticDelay::~ScopedSyntheticDelay() { - delay_impl_->EndParallel(end_time_); -} - -base::trace_event::TraceEventSyntheticDelay* GetOrCreateDelay( - const char* name, - base::subtle::AtomicWord* impl_ptr) { - base::trace_event::TraceEventSyntheticDelay* delay_impl = - reinterpret_cast( - base::subtle::Acquire_Load(impl_ptr)); - if (!delay_impl) { - delay_impl = - base::trace_event::TraceEventSyntheticDelayRegistry::GetInstance() - ->GetOrCreateDelay(name); - base::subtle::Release_Store( - impl_ptr, reinterpret_cast(delay_impl)); - } - return delay_impl; -} - -} // namespace trace_event_internal diff --git a/base/trace_event/trace_event_synthetic_delay.h b/base/trace_event/trace_event_synthetic_delay.h deleted file mode 100644 index e86f9eee2c6871ade60567fcf846dd94d547ede5..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_synthetic_delay.h +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2014 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. - -// The synthetic delay framework makes it possible to dynamically inject -// arbitrary delays into into different parts of the codebase. This can be used, -// for instance, for testing various task scheduling algorithms. -// -// The delays are specified in terms of a target duration for a given block of -// code. If the code executes faster than the duration, the thread is made to -// sleep until the deadline is met. -// -// Code can be instrumented for delays with two sets of macros. First, for -// delays that should apply within a scope, use the following macro: -// -// TRACE_EVENT_SYNTHETIC_DELAY("cc.LayerTreeHost.DrawAndSwap"); -// -// For delaying operations that span multiple scopes, use: -// -// TRACE_EVENT_SYNTHETIC_DELAY_BEGIN("cc.Scheduler.BeginMainFrame"); -// ... -// TRACE_EVENT_SYNTHETIC_DELAY_END("cc.Scheduler.BeginMainFrame"); -// -// Here BEGIN establishes the start time for the delay and END executes the -// delay based on the remaining time. If BEGIN is called multiple times in a -// row, END should be called a corresponding number of times. Only the last -// call to END will have an effect. -// -// Note that a single delay may begin on one thread and end on another. This -// implies that a single delay cannot not be applied in several threads at once. - -#ifndef BASE_TRACE_EVENT_TRACE_EVENT_SYNTHETIC_DELAY_H_ -#define BASE_TRACE_EVENT_TRACE_EVENT_SYNTHETIC_DELAY_H_ - -#include "base/atomicops.h" -#include "base/macros.h" -#include "base/synchronization/lock.h" -#include "base/time/time.h" -#include "base/trace_event/trace_event.h" - -// Apply a named delay in the current scope. -#define TRACE_EVENT_SYNTHETIC_DELAY(name) \ - static base::subtle::AtomicWord INTERNAL_TRACE_EVENT_UID(impl_ptr) = 0; \ - trace_event_internal::ScopedSyntheticDelay INTERNAL_TRACE_EVENT_UID(delay)( \ - name, &INTERNAL_TRACE_EVENT_UID(impl_ptr)); - -// Begin a named delay, establishing its timing start point. May be called -// multiple times as long as the calls to TRACE_EVENT_SYNTHETIC_DELAY_END are -// balanced. Only the first call records the timing start point. -#define TRACE_EVENT_SYNTHETIC_DELAY_BEGIN(name) \ - do { \ - static base::subtle::AtomicWord impl_ptr = 0; \ - trace_event_internal::GetOrCreateDelay(name, &impl_ptr)->Begin(); \ - } while (false) - -// End a named delay. The delay is applied only if this call matches the -// first corresponding call to TRACE_EVENT_SYNTHETIC_DELAY_BEGIN with the -// same delay. -#define TRACE_EVENT_SYNTHETIC_DELAY_END(name) \ - do { \ - static base::subtle::AtomicWord impl_ptr = 0; \ - trace_event_internal::GetOrCreateDelay(name, &impl_ptr)->End(); \ - } while (false) - -namespace base { -namespace trace_event { - -// Time source for computing delay durations. Used for testing. -class TRACE_EVENT_API_CLASS_EXPORT TraceEventSyntheticDelayClock { - public: - TraceEventSyntheticDelayClock(); - virtual ~TraceEventSyntheticDelayClock(); - virtual base::TimeTicks Now() = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(TraceEventSyntheticDelayClock); -}; - -// Single delay point instance. -class TRACE_EVENT_API_CLASS_EXPORT TraceEventSyntheticDelay { - public: - enum Mode { - STATIC, // Apply the configured delay every time. - ONE_SHOT, // Apply the configured delay just once. - ALTERNATING // Apply the configured delay every other time. - }; - - // Returns an existing named delay instance or creates a new one with |name|. - static TraceEventSyntheticDelay* Lookup(const std::string& name); - - void SetTargetDuration(TimeDelta target_duration); - void SetMode(Mode mode); - void SetClock(TraceEventSyntheticDelayClock* clock); - - // Begin the delay, establishing its timing start point. May be called - // multiple times as long as the calls to End() are balanced. Only the first - // call records the timing start point. - void Begin(); - - // End the delay. The delay is applied only if this call matches the first - // corresponding call to Begin() with the same delay. - void End(); - - // Begin a parallel instance of the delay. Several parallel instances may be - // active simultaneously and will complete independently. The computed end - // time for the delay is stored in |out_end_time|, which should later be - // passed to EndParallel(). - void BeginParallel(base::TimeTicks* out_end_time); - - // End a previously started parallel delay. |end_time| is the delay end point - // computed by BeginParallel(). - void EndParallel(base::TimeTicks end_time); - - private: - TraceEventSyntheticDelay(); - ~TraceEventSyntheticDelay(); - friend class TraceEventSyntheticDelayRegistry; - - void Initialize(const std::string& name, - TraceEventSyntheticDelayClock* clock); - base::TimeTicks CalculateEndTimeLocked(base::TimeTicks start_time); - void ApplyDelay(base::TimeTicks end_time); - - Lock lock_; - Mode mode_; - std::string name_; - int begin_count_; - int trigger_count_; - base::TimeTicks end_time_; - base::TimeDelta target_duration_; - TraceEventSyntheticDelayClock* clock_; - - DISALLOW_COPY_AND_ASSIGN(TraceEventSyntheticDelay); -}; - -// Set the target durations of all registered synthetic delay points to zero. -TRACE_EVENT_API_CLASS_EXPORT void ResetTraceEventSyntheticDelays(); - -} // namespace trace_event -} // namespace base - -namespace trace_event_internal { - -// Helper class for scoped delays. Do not use directly. -class TRACE_EVENT_API_CLASS_EXPORT ScopedSyntheticDelay { - public: - explicit ScopedSyntheticDelay(const char* name, - base::subtle::AtomicWord* impl_ptr); - ~ScopedSyntheticDelay(); - - private: - base::trace_event::TraceEventSyntheticDelay* delay_impl_; - base::TimeTicks end_time_; - - DISALLOW_COPY_AND_ASSIGN(ScopedSyntheticDelay); -}; - -// Helper for registering delays. Do not use directly. -TRACE_EVENT_API_CLASS_EXPORT base::trace_event::TraceEventSyntheticDelay* - GetOrCreateDelay(const char* name, base::subtle::AtomicWord* impl_ptr); - -} // namespace trace_event_internal - -#endif // BASE_TRACE_EVENT_TRACE_EVENT_SYNTHETIC_DELAY_H_ diff --git a/base/trace_event/trace_event_synthetic_delay_unittest.cc b/base/trace_event/trace_event_synthetic_delay_unittest.cc deleted file mode 100644 index 97a4580b3bbad69810a38c2ca33d291b6e8060fe..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_synthetic_delay_unittest.cc +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2014 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. - -#include "base/trace_event/trace_event_synthetic_delay.h" - -#include - -#include "base/macros.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace trace_event { -namespace { - -const int kTargetDurationMs = 100; -// Allow some leeway in timings to make it possible to run these tests with a -// wall clock time source too. -const int kShortDurationMs = 10; - -} // namespace - -class TraceEventSyntheticDelayTest : public testing::Test, - public TraceEventSyntheticDelayClock { - public: - TraceEventSyntheticDelayTest() {} - ~TraceEventSyntheticDelayTest() override { ResetTraceEventSyntheticDelays(); } - - // TraceEventSyntheticDelayClock implementation. - base::TimeTicks Now() override { - AdvanceTime(base::TimeDelta::FromMilliseconds(kShortDurationMs / 10)); - return now_; - } - - TraceEventSyntheticDelay* ConfigureDelay(const char* name) { - TraceEventSyntheticDelay* delay = TraceEventSyntheticDelay::Lookup(name); - delay->SetClock(this); - delay->SetTargetDuration( - base::TimeDelta::FromMilliseconds(kTargetDurationMs)); - return delay; - } - - void AdvanceTime(base::TimeDelta delta) { now_ += delta; } - - int64_t TestFunction() { - base::TimeTicks start = Now(); - { TRACE_EVENT_SYNTHETIC_DELAY("test.Delay"); } - return (Now() - start).InMilliseconds(); - } - - int64_t AsyncTestFunctionBegin() { - base::TimeTicks start = Now(); - { TRACE_EVENT_SYNTHETIC_DELAY_BEGIN("test.AsyncDelay"); } - return (Now() - start).InMilliseconds(); - } - - int64_t AsyncTestFunctionEnd() { - base::TimeTicks start = Now(); - { TRACE_EVENT_SYNTHETIC_DELAY_END("test.AsyncDelay"); } - return (Now() - start).InMilliseconds(); - } - - private: - base::TimeTicks now_; - - DISALLOW_COPY_AND_ASSIGN(TraceEventSyntheticDelayTest); -}; - -TEST_F(TraceEventSyntheticDelayTest, StaticDelay) { - TraceEventSyntheticDelay* delay = ConfigureDelay("test.Delay"); - delay->SetMode(TraceEventSyntheticDelay::STATIC); - EXPECT_GE(TestFunction(), kTargetDurationMs); -} - -TEST_F(TraceEventSyntheticDelayTest, OneShotDelay) { - TraceEventSyntheticDelay* delay = ConfigureDelay("test.Delay"); - delay->SetMode(TraceEventSyntheticDelay::ONE_SHOT); - EXPECT_GE(TestFunction(), kTargetDurationMs); - EXPECT_LT(TestFunction(), kShortDurationMs); - - delay->SetTargetDuration( - base::TimeDelta::FromMilliseconds(kTargetDurationMs)); - EXPECT_GE(TestFunction(), kTargetDurationMs); -} - -TEST_F(TraceEventSyntheticDelayTest, AlternatingDelay) { - TraceEventSyntheticDelay* delay = ConfigureDelay("test.Delay"); - delay->SetMode(TraceEventSyntheticDelay::ALTERNATING); - EXPECT_GE(TestFunction(), kTargetDurationMs); - EXPECT_LT(TestFunction(), kShortDurationMs); - EXPECT_GE(TestFunction(), kTargetDurationMs); - EXPECT_LT(TestFunction(), kShortDurationMs); -} - -TEST_F(TraceEventSyntheticDelayTest, AsyncDelay) { - ConfigureDelay("test.AsyncDelay"); - EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs); - EXPECT_GE(AsyncTestFunctionEnd(), kTargetDurationMs / 2); -} - -TEST_F(TraceEventSyntheticDelayTest, AsyncDelayExceeded) { - ConfigureDelay("test.AsyncDelay"); - EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs); - AdvanceTime(base::TimeDelta::FromMilliseconds(kTargetDurationMs)); - EXPECT_LT(AsyncTestFunctionEnd(), kShortDurationMs); -} - -TEST_F(TraceEventSyntheticDelayTest, AsyncDelayNoActivation) { - ConfigureDelay("test.AsyncDelay"); - EXPECT_LT(AsyncTestFunctionEnd(), kShortDurationMs); -} - -TEST_F(TraceEventSyntheticDelayTest, AsyncDelayNested) { - ConfigureDelay("test.AsyncDelay"); - EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs); - EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs); - EXPECT_LT(AsyncTestFunctionEnd(), kShortDurationMs); - EXPECT_GE(AsyncTestFunctionEnd(), kTargetDurationMs / 2); -} - -TEST_F(TraceEventSyntheticDelayTest, AsyncDelayUnbalanced) { - ConfigureDelay("test.AsyncDelay"); - EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs); - EXPECT_GE(AsyncTestFunctionEnd(), kTargetDurationMs / 2); - EXPECT_LT(AsyncTestFunctionEnd(), kShortDurationMs); - - EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs); - EXPECT_GE(AsyncTestFunctionEnd(), kTargetDurationMs / 2); -} - -TEST_F(TraceEventSyntheticDelayTest, ResetDelays) { - ConfigureDelay("test.Delay"); - ResetTraceEventSyntheticDelays(); - EXPECT_LT(TestFunction(), kShortDurationMs); -} - -TEST_F(TraceEventSyntheticDelayTest, BeginParallel) { - TraceEventSyntheticDelay* delay = ConfigureDelay("test.AsyncDelay"); - base::TimeTicks end_times[2]; - base::TimeTicks start_time = Now(); - - delay->BeginParallel(&end_times[0]); - EXPECT_FALSE(end_times[0].is_null()); - - delay->BeginParallel(&end_times[1]); - EXPECT_FALSE(end_times[1].is_null()); - - delay->EndParallel(end_times[0]); - EXPECT_GE((Now() - start_time).InMilliseconds(), kTargetDurationMs); - - start_time = Now(); - delay->EndParallel(end_times[1]); - EXPECT_LT((Now() - start_time).InMilliseconds(), kShortDurationMs); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/trace_event_system_stats_monitor.h b/base/trace_event/trace_event_system_stats_monitor.h deleted file mode 100644 index 14aa5681fe46e0c484d33fafbdc50a81d693bc8f..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_system_stats_monitor.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2013 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 BASE_TRACE_EVENT_TRACE_EVENT_SYSTEM_STATS_MONITOR_H_ -#define BASE_TRACE_EVENT_TRACE_EVENT_SYSTEM_STATS_MONITOR_H_ - -#include "base/base_export.h" -#include "base/gtest_prod_util.h" -#include "base/macros.h" -#include "base/memory/ref_counted.h" -#include "base/memory/weak_ptr.h" -#include "base/process/process_metrics.h" -#include "base/timer/timer.h" -#include "base/trace_event/trace_log.h" - -namespace base { - -class SingleThreadTaskRunner; - -namespace trace_event { - -// Watches for chrome://tracing to be enabled or disabled. When tracing is -// enabled, also enables system events profiling. This class is the preferred -// way to turn system tracing on and off. -class BASE_EXPORT TraceEventSystemStatsMonitor - : public TraceLog::EnabledStateObserver { - public: - // Length of time interval between stat profiles. - static const int kSamplingIntervalMilliseconds = 2000; - - // |task_runner| must be the primary thread for the client - // process, e.g. the UI thread in a browser. - explicit TraceEventSystemStatsMonitor( - scoped_refptr task_runner); - - ~TraceEventSystemStatsMonitor() override; - - // base::trace_event::TraceLog::EnabledStateChangedObserver overrides: - void OnTraceLogEnabled() override; - void OnTraceLogDisabled() override; - - // Retrieves system profiling at the current time. - void DumpSystemStats(); - - private: - FRIEND_TEST_ALL_PREFIXES(TraceSystemStatsMonitorTest, - TraceEventSystemStatsMonitor); - - bool IsTimerRunningForTest() const; - - void StartProfiling(); - - void StopProfiling(); - - // Ensures the observer starts and stops tracing on the primary thread. - scoped_refptr task_runner_; - - // Timer to schedule system profile dumps. - RepeatingTimer dump_timer_; - - WeakPtrFactory weak_factory_; - - DISALLOW_COPY_AND_ASSIGN(TraceEventSystemStatsMonitor); -}; - -// Converts system memory profiling stats in |input| to -// trace event compatible JSON and appends to |output|. Visible for testing. -BASE_EXPORT void AppendSystemProfileAsTraceFormat(const SystemMetrics& - system_stats, - std::string* output); - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_EVENT_SYSTEM_STATS_MONITOR_H_ diff --git a/base/trace_event/trace_event_unittest.cc b/base/trace_event/trace_event_unittest.cc deleted file mode 100644 index 7a30e4ee571c0f8a50bd3b08f2d862dceb549ed8..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_event_unittest.cc +++ /dev/null @@ -1,3220 +0,0 @@ -// Copyright (c) 2012 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. - -#include "base/trace_event/trace_event.h" - -#include -#include -#include - -#include -#include -#include - -#include "base/bind.h" -#include "base/command_line.h" -#include "base/json/json_reader.h" -#include "base/json/json_writer.h" -#include "base/location.h" -#include "base/macros.h" -#include "base/memory/ptr_util.h" -#include "base/memory/ref_counted_memory.h" -#include "base/memory/singleton.h" -#include "base/process/process_handle.h" -#include "base/single_thread_task_runner.h" -#include "base/stl_util.h" -#include "base/strings/pattern.h" -#include "base/strings/stringprintf.h" -#include "base/synchronization/waitable_event.h" -#include "base/threading/platform_thread.h" -#include "base/threading/thread.h" -#include "base/time/time.h" -#include "base/trace_event/event_name_filter.h" -#include "base/trace_event/heap_profiler_event_filter.h" -#include "base/trace_event/trace_buffer.h" -#include "base/trace_event/trace_event.h" -#include "base/trace_event/trace_event_filter.h" -#include "base/trace_event/trace_event_filter_test_utils.h" -#include "base/trace_event/trace_event_synthetic_delay.h" -#include "base/values.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace trace_event { - -namespace { - -enum CompareOp { - IS_EQUAL, - IS_NOT_EQUAL, -}; - -struct JsonKeyValue { - const char* key; - const char* value; - CompareOp op; -}; - -const int kThreadId = 42; -const int kAsyncId = 5; -const char kAsyncIdStr[] = "0x5"; -const int kAsyncId2 = 6; -const char kAsyncId2Str[] = "0x6"; -const int kFlowId = 7; -const char kFlowIdStr[] = "0x7"; - -const char kRecordAllCategoryFilter[] = "*"; - -class TraceEventTestFixture : public testing::Test { - public: - void OnTraceDataCollected( - WaitableEvent* flush_complete_event, - const scoped_refptr& events_str, - bool has_more_events); - DictionaryValue* FindMatchingTraceEntry(const JsonKeyValue* key_values); - DictionaryValue* FindNamePhase(const char* name, const char* phase); - DictionaryValue* FindNamePhaseKeyValue(const char* name, - const char* phase, - const char* key, - const char* value); - void DropTracedMetadataRecords(); - bool FindMatchingValue(const char* key, - const char* value); - bool FindNonMatchingValue(const char* key, - const char* value); - void Clear() { - trace_parsed_.Clear(); - json_output_.json_output.clear(); - } - - void BeginTrace() { - BeginSpecificTrace("*"); - } - - void BeginSpecificTrace(const std::string& filter) { - TraceLog::GetInstance()->SetEnabled(TraceConfig(filter, ""), - TraceLog::RECORDING_MODE); - } - - void CancelTrace() { - WaitableEvent flush_complete_event( - WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - CancelTraceAsync(&flush_complete_event); - flush_complete_event.Wait(); - } - - void EndTraceAndFlush() { - num_flush_callbacks_ = 0; - WaitableEvent flush_complete_event( - WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - EndTraceAndFlushAsync(&flush_complete_event); - flush_complete_event.Wait(); - } - - // Used when testing thread-local buffers which requires the thread initiating - // flush to have a message loop. - void EndTraceAndFlushInThreadWithMessageLoop() { - WaitableEvent flush_complete_event( - WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - Thread flush_thread("flush"); - flush_thread.Start(); - flush_thread.task_runner()->PostTask( - FROM_HERE, base::Bind(&TraceEventTestFixture::EndTraceAndFlushAsync, - base::Unretained(this), &flush_complete_event)); - flush_complete_event.Wait(); - } - - void CancelTraceAsync(WaitableEvent* flush_complete_event) { - TraceLog::GetInstance()->CancelTracing( - base::Bind(&TraceEventTestFixture::OnTraceDataCollected, - base::Unretained(static_cast(this)), - base::Unretained(flush_complete_event))); - } - - void EndTraceAndFlushAsync(WaitableEvent* flush_complete_event) { - TraceLog::GetInstance()->SetDisabled(TraceLog::RECORDING_MODE | - TraceLog::FILTERING_MODE); - TraceLog::GetInstance()->Flush( - base::Bind(&TraceEventTestFixture::OnTraceDataCollected, - base::Unretained(static_cast(this)), - base::Unretained(flush_complete_event))); - } - - void SetUp() override { - const char* name = PlatformThread::GetName(); - old_thread_name_ = name ? strdup(name) : NULL; - - TraceLog::DeleteForTesting(); - TraceLog* tracelog = TraceLog::GetInstance(); - ASSERT_TRUE(tracelog); - ASSERT_FALSE(tracelog->IsEnabled()); - trace_buffer_.SetOutputCallback(json_output_.GetCallback()); - num_flush_callbacks_ = 0; - } - void TearDown() override { - if (TraceLog::GetInstance()) - EXPECT_FALSE(TraceLog::GetInstance()->IsEnabled()); - PlatformThread::SetName(old_thread_name_ ? old_thread_name_ : ""); - free(old_thread_name_); - old_thread_name_ = NULL; - // We want our singleton torn down after each test. - TraceLog::DeleteForTesting(); - } - - char* old_thread_name_; - ListValue trace_parsed_; - TraceResultBuffer trace_buffer_; - TraceResultBuffer::SimpleOutput json_output_; - size_t num_flush_callbacks_; - - private: - // We want our singleton torn down after each test. - ShadowingAtExitManager at_exit_manager_; - Lock lock_; -}; - -void TraceEventTestFixture::OnTraceDataCollected( - WaitableEvent* flush_complete_event, - const scoped_refptr& events_str, - bool has_more_events) { - num_flush_callbacks_++; - if (num_flush_callbacks_ > 1) { - EXPECT_FALSE(events_str->data().empty()); - } - AutoLock lock(lock_); - json_output_.json_output.clear(); - trace_buffer_.Start(); - trace_buffer_.AddFragment(events_str->data()); - trace_buffer_.Finish(); - - std::unique_ptr root = base::JSONReader::Read( - json_output_.json_output, JSON_PARSE_RFC | JSON_DETACHABLE_CHILDREN); - - if (!root.get()) { - LOG(ERROR) << json_output_.json_output; - } - - ListValue* root_list = NULL; - ASSERT_TRUE(root.get()); - ASSERT_TRUE(root->GetAsList(&root_list)); - - // Move items into our aggregate collection - while (root_list->GetSize()) { - std::unique_ptr item; - root_list->Remove(0, &item); - trace_parsed_.Append(std::move(item)); - } - - if (!has_more_events) - flush_complete_event->Signal(); -} - -static bool CompareJsonValues(const std::string& lhs, - const std::string& rhs, - CompareOp op) { - switch (op) { - case IS_EQUAL: - return lhs == rhs; - case IS_NOT_EQUAL: - return lhs != rhs; - default: - CHECK(0); - } - return false; -} - -static bool IsKeyValueInDict(const JsonKeyValue* key_value, - DictionaryValue* dict) { - Value* value = NULL; - std::string value_str; - if (dict->Get(key_value->key, &value) && - value->GetAsString(&value_str) && - CompareJsonValues(value_str, key_value->value, key_value->op)) - return true; - - // Recurse to test arguments - DictionaryValue* args_dict = NULL; - dict->GetDictionary("args", &args_dict); - if (args_dict) - return IsKeyValueInDict(key_value, args_dict); - - return false; -} - -static bool IsAllKeyValueInDict(const JsonKeyValue* key_values, - DictionaryValue* dict) { - // Scan all key_values, they must all be present and equal. - while (key_values && key_values->key) { - if (!IsKeyValueInDict(key_values, dict)) - return false; - ++key_values; - } - return true; -} - -DictionaryValue* TraceEventTestFixture::FindMatchingTraceEntry( - const JsonKeyValue* key_values) { - // Scan all items - size_t trace_parsed_count = trace_parsed_.GetSize(); - for (size_t i = 0; i < trace_parsed_count; i++) { - Value* value = NULL; - trace_parsed_.Get(i, &value); - if (!value || value->GetType() != Value::Type::DICTIONARY) - continue; - DictionaryValue* dict = static_cast(value); - - if (IsAllKeyValueInDict(key_values, dict)) - return dict; - } - return NULL; -} - -void TraceEventTestFixture::DropTracedMetadataRecords() { - std::unique_ptr old_trace_parsed(trace_parsed_.CreateDeepCopy()); - size_t old_trace_parsed_size = old_trace_parsed->GetSize(); - trace_parsed_.Clear(); - - for (size_t i = 0; i < old_trace_parsed_size; i++) { - Value* value = nullptr; - old_trace_parsed->Get(i, &value); - if (!value || value->GetType() != Value::Type::DICTIONARY) { - trace_parsed_.Append(value->CreateDeepCopy()); - continue; - } - DictionaryValue* dict = static_cast(value); - std::string tmp; - if (dict->GetString("ph", &tmp) && tmp == "M") - continue; - - trace_parsed_.Append(value->CreateDeepCopy()); - } -} - -DictionaryValue* TraceEventTestFixture::FindNamePhase(const char* name, - const char* phase) { - JsonKeyValue key_values[] = { - {"name", name, IS_EQUAL}, - {"ph", phase, IS_EQUAL}, - {0, 0, IS_EQUAL} - }; - return FindMatchingTraceEntry(key_values); -} - -DictionaryValue* TraceEventTestFixture::FindNamePhaseKeyValue( - const char* name, - const char* phase, - const char* key, - const char* value) { - JsonKeyValue key_values[] = { - {"name", name, IS_EQUAL}, - {"ph", phase, IS_EQUAL}, - {key, value, IS_EQUAL}, - {0, 0, IS_EQUAL} - }; - return FindMatchingTraceEntry(key_values); -} - -bool TraceEventTestFixture::FindMatchingValue(const char* key, - const char* value) { - JsonKeyValue key_values[] = { - {key, value, IS_EQUAL}, - {0, 0, IS_EQUAL} - }; - return FindMatchingTraceEntry(key_values); -} - -bool TraceEventTestFixture::FindNonMatchingValue(const char* key, - const char* value) { - JsonKeyValue key_values[] = { - {key, value, IS_NOT_EQUAL}, - {0, 0, IS_EQUAL} - }; - return FindMatchingTraceEntry(key_values); -} - -bool IsStringInDict(const char* string_to_match, const DictionaryValue* dict) { - for (DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { - if (it.key().find(string_to_match) != std::string::npos) - return true; - - std::string value_str; - it.value().GetAsString(&value_str); - if (value_str.find(string_to_match) != std::string::npos) - return true; - } - - // Recurse to test arguments - const DictionaryValue* args_dict = NULL; - dict->GetDictionary("args", &args_dict); - if (args_dict) - return IsStringInDict(string_to_match, args_dict); - - return false; -} - -const DictionaryValue* FindTraceEntry( - const ListValue& trace_parsed, - const char* string_to_match, - const DictionaryValue* match_after_this_item = NULL) { - // Scan all items - size_t trace_parsed_count = trace_parsed.GetSize(); - for (size_t i = 0; i < trace_parsed_count; i++) { - const Value* value = NULL; - trace_parsed.Get(i, &value); - if (match_after_this_item) { - if (value == match_after_this_item) - match_after_this_item = NULL; - continue; - } - if (!value || value->GetType() != Value::Type::DICTIONARY) - continue; - const DictionaryValue* dict = static_cast(value); - - if (IsStringInDict(string_to_match, dict)) - return dict; - } - return NULL; -} - -std::vector FindTraceEntries( - const ListValue& trace_parsed, - const char* string_to_match) { - std::vector hits; - size_t trace_parsed_count = trace_parsed.GetSize(); - for (size_t i = 0; i < trace_parsed_count; i++) { - const Value* value = NULL; - trace_parsed.Get(i, &value); - if (!value || value->GetType() != Value::Type::DICTIONARY) - continue; - const DictionaryValue* dict = static_cast(value); - - if (IsStringInDict(string_to_match, dict)) - hits.push_back(dict); - } - return hits; -} - -const char kControlCharacters[] = "\001\002\003\n\r"; - -void TraceWithAllMacroVariants(WaitableEvent* task_complete_event) { - { - TRACE_EVENT0("all", "TRACE_EVENT0 call"); - TRACE_EVENT1("all", "TRACE_EVENT1 call", "name1", "value1"); - TRACE_EVENT2("all", "TRACE_EVENT2 call", - "name1", "\"value1\"", - "name2", "value\\2"); - - TRACE_EVENT_INSTANT0("all", "TRACE_EVENT_INSTANT0 call", - TRACE_EVENT_SCOPE_GLOBAL); - TRACE_EVENT_INSTANT1("all", "TRACE_EVENT_INSTANT1 call", - TRACE_EVENT_SCOPE_PROCESS, "name1", "value1"); - TRACE_EVENT_INSTANT2("all", "TRACE_EVENT_INSTANT2 call", - TRACE_EVENT_SCOPE_THREAD, - "name1", "value1", - "name2", "value2"); - - TRACE_EVENT_BEGIN0("all", "TRACE_EVENT_BEGIN0 call"); - TRACE_EVENT_BEGIN1("all", "TRACE_EVENT_BEGIN1 call", "name1", "value1"); - TRACE_EVENT_BEGIN2("all", "TRACE_EVENT_BEGIN2 call", - "name1", "value1", - "name2", "value2"); - - TRACE_EVENT_END0("all", "TRACE_EVENT_END0 call"); - TRACE_EVENT_END1("all", "TRACE_EVENT_END1 call", "name1", "value1"); - TRACE_EVENT_END2("all", "TRACE_EVENT_END2 call", - "name1", "value1", - "name2", "value2"); - - TRACE_EVENT_ASYNC_BEGIN0("all", "TRACE_EVENT_ASYNC_BEGIN0 call", kAsyncId); - TRACE_EVENT_ASYNC_BEGIN1("all", "TRACE_EVENT_ASYNC_BEGIN1 call", kAsyncId, - "name1", "value1"); - TRACE_EVENT_ASYNC_BEGIN2("all", "TRACE_EVENT_ASYNC_BEGIN2 call", kAsyncId, - "name1", "value1", - "name2", "value2"); - - TRACE_EVENT_ASYNC_STEP_INTO0("all", "TRACE_EVENT_ASYNC_STEP_INTO0 call", - kAsyncId, "step_begin1"); - TRACE_EVENT_ASYNC_STEP_INTO1("all", "TRACE_EVENT_ASYNC_STEP_INTO1 call", - kAsyncId, "step_begin2", "name1", "value1"); - - TRACE_EVENT_ASYNC_END0("all", "TRACE_EVENT_ASYNC_END0 call", kAsyncId); - TRACE_EVENT_ASYNC_END1("all", "TRACE_EVENT_ASYNC_END1 call", kAsyncId, - "name1", "value1"); - TRACE_EVENT_ASYNC_END2("all", "TRACE_EVENT_ASYNC_END2 call", kAsyncId, - "name1", "value1", - "name2", "value2"); - - TRACE_EVENT_FLOW_BEGIN0("all", "TRACE_EVENT_FLOW_BEGIN0 call", kFlowId); - TRACE_EVENT_FLOW_STEP0("all", "TRACE_EVENT_FLOW_STEP0 call", - kFlowId, "step1"); - TRACE_EVENT_FLOW_END_BIND_TO_ENCLOSING0("all", - "TRACE_EVENT_FLOW_END_BIND_TO_ENCLOSING0 call", kFlowId); - - TRACE_COUNTER1("all", "TRACE_COUNTER1 call", 31415); - TRACE_COUNTER2("all", "TRACE_COUNTER2 call", - "a", 30000, - "b", 1415); - - TRACE_COUNTER_WITH_TIMESTAMP1("all", "TRACE_COUNTER_WITH_TIMESTAMP1 call", - TimeTicks::FromInternalValue(42), 31415); - TRACE_COUNTER_WITH_TIMESTAMP2("all", "TRACE_COUNTER_WITH_TIMESTAMP2 call", - TimeTicks::FromInternalValue(42), - "a", 30000, "b", 1415); - - TRACE_COUNTER_ID1("all", "TRACE_COUNTER_ID1 call", 0x319009, 31415); - TRACE_COUNTER_ID2("all", "TRACE_COUNTER_ID2 call", 0x319009, - "a", 30000, "b", 1415); - - TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0("all", - "TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call", - kAsyncId, kThreadId, TimeTicks::FromInternalValue(12345)); - TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0("all", - "TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0 call", - kAsyncId, kThreadId, TimeTicks::FromInternalValue(23456)); - - TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0("all", - "TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call", - kAsyncId2, kThreadId, TimeTicks::FromInternalValue(34567)); - TRACE_EVENT_ASYNC_STEP_PAST0("all", "TRACE_EVENT_ASYNC_STEP_PAST0 call", - kAsyncId2, "step_end1"); - TRACE_EVENT_ASYNC_STEP_PAST1("all", "TRACE_EVENT_ASYNC_STEP_PAST1 call", - kAsyncId2, "step_end2", "name1", "value1"); - - TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0("all", - "TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0 call", - kAsyncId2, kThreadId, TimeTicks::FromInternalValue(45678)); - - TRACE_EVENT_OBJECT_CREATED_WITH_ID("all", "tracked object 1", 0x42); - TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( - "all", "tracked object 1", 0x42, "hello"); - TRACE_EVENT_OBJECT_DELETED_WITH_ID("all", "tracked object 1", 0x42); - - TraceScopedTrackableObject trackable("all", "tracked object 2", - 0x2128506); - trackable.snapshot("world"); - - TRACE_EVENT_OBJECT_CREATED_WITH_ID( - "all", "tracked object 3", TRACE_ID_WITH_SCOPE("scope", 0x42)); - TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( - "all", "tracked object 3", TRACE_ID_WITH_SCOPE("scope", 0x42), "hello"); - TRACE_EVENT_OBJECT_DELETED_WITH_ID( - "all", "tracked object 3", TRACE_ID_WITH_SCOPE("scope", 0x42)); - - TRACE_EVENT1(kControlCharacters, kControlCharacters, - kControlCharacters, kControlCharacters); - - uint64_t context_id = 0x20151021; - - TRACE_EVENT_ENTER_CONTEXT("all", "TRACE_EVENT_ENTER_CONTEXT call", - TRACE_ID_WITH_SCOPE("scope", context_id)); - TRACE_EVENT_LEAVE_CONTEXT("all", "TRACE_EVENT_LEAVE_CONTEXT call", - TRACE_ID_WITH_SCOPE("scope", context_id)); - TRACE_EVENT_SCOPED_CONTEXT("disabled-by-default-cat", - "TRACE_EVENT_SCOPED_CONTEXT disabled call", - context_id); - TRACE_EVENT_SCOPED_CONTEXT("all", "TRACE_EVENT_SCOPED_CONTEXT call", - context_id); - - TRACE_LINK_IDS("all", "TRACE_LINK_IDS simple call", 0x1000, 0x2000); - TRACE_LINK_IDS("all", "TRACE_LINK_IDS scoped call", - TRACE_ID_WITH_SCOPE("scope 1", 0x1000), - TRACE_ID_WITH_SCOPE("scope 2", 0x2000)); - TRACE_LINK_IDS("all", "TRACE_LINK_IDS to a local ID", 0x1000, - TRACE_ID_LOCAL(0x2000)); - TRACE_LINK_IDS("all", "TRACE_LINK_IDS to a global ID", 0x1000, - TRACE_ID_GLOBAL(0x2000)); - TRACE_LINK_IDS("all", "TRACE_LINK_IDS to a composite ID", 0x1000, - TRACE_ID_WITH_SCOPE("scope 1", 0x2000, 0x3000)); - - TRACE_EVENT_ASYNC_BEGIN0("all", "async default process scope", 0x1000); - TRACE_EVENT_ASYNC_BEGIN0("all", "async local id", TRACE_ID_LOCAL(0x2000)); - TRACE_EVENT_ASYNC_BEGIN0("all", "async global id", TRACE_ID_GLOBAL(0x3000)); - TRACE_EVENT_ASYNC_BEGIN0("all", "async global id with scope string", - TRACE_ID_WITH_SCOPE("scope string", - TRACE_ID_GLOBAL(0x4000))); - } // Scope close causes TRACE_EVENT0 etc to send their END events. - - if (task_complete_event) - task_complete_event->Signal(); -} - -void ValidateAllTraceMacrosCreatedData(const ListValue& trace_parsed) { - const DictionaryValue* item = NULL; - -#define EXPECT_FIND_(string) \ - item = FindTraceEntry(trace_parsed, string); \ - EXPECT_TRUE(item); -#define EXPECT_NOT_FIND_(string) \ - item = FindTraceEntry(trace_parsed, string); \ - EXPECT_FALSE(item); -#define EXPECT_SUB_FIND_(string) \ - if (item) \ - EXPECT_TRUE(IsStringInDict(string, item)); - - EXPECT_FIND_("TRACE_EVENT0 call"); - { - std::string ph; - std::string ph_end; - EXPECT_TRUE((item = FindTraceEntry(trace_parsed, "TRACE_EVENT0 call"))); - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("X", ph); - item = FindTraceEntry(trace_parsed, "TRACE_EVENT0 call", item); - EXPECT_FALSE(item); - } - EXPECT_FIND_("TRACE_EVENT1 call"); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - EXPECT_FIND_("TRACE_EVENT2 call"); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("\"value1\""); - EXPECT_SUB_FIND_("name2"); - EXPECT_SUB_FIND_("value\\2"); - - EXPECT_FIND_("TRACE_EVENT_INSTANT0 call"); - { - std::string scope; - EXPECT_TRUE((item && item->GetString("s", &scope))); - EXPECT_EQ("g", scope); - } - EXPECT_FIND_("TRACE_EVENT_INSTANT1 call"); - { - std::string scope; - EXPECT_TRUE((item && item->GetString("s", &scope))); - EXPECT_EQ("p", scope); - } - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - EXPECT_FIND_("TRACE_EVENT_INSTANT2 call"); - { - std::string scope; - EXPECT_TRUE((item && item->GetString("s", &scope))); - EXPECT_EQ("t", scope); - } - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - EXPECT_SUB_FIND_("name2"); - EXPECT_SUB_FIND_("value2"); - - EXPECT_FIND_("TRACE_EVENT_BEGIN0 call"); - EXPECT_FIND_("TRACE_EVENT_BEGIN1 call"); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - EXPECT_FIND_("TRACE_EVENT_BEGIN2 call"); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - EXPECT_SUB_FIND_("name2"); - EXPECT_SUB_FIND_("value2"); - - EXPECT_FIND_("TRACE_EVENT_END0 call"); - EXPECT_FIND_("TRACE_EVENT_END1 call"); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - EXPECT_FIND_("TRACE_EVENT_END2 call"); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - EXPECT_SUB_FIND_("name2"); - EXPECT_SUB_FIND_("value2"); - - EXPECT_FIND_("TRACE_EVENT_ASYNC_BEGIN0 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kAsyncIdStr); - EXPECT_FIND_("TRACE_EVENT_ASYNC_BEGIN1 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kAsyncIdStr); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - EXPECT_FIND_("TRACE_EVENT_ASYNC_BEGIN2 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kAsyncIdStr); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - EXPECT_SUB_FIND_("name2"); - EXPECT_SUB_FIND_("value2"); - - EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_INTO0 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kAsyncIdStr); - EXPECT_SUB_FIND_("step_begin1"); - EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_INTO1 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kAsyncIdStr); - EXPECT_SUB_FIND_("step_begin2"); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - - EXPECT_FIND_("TRACE_EVENT_ASYNC_END0 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kAsyncIdStr); - EXPECT_FIND_("TRACE_EVENT_ASYNC_END1 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kAsyncIdStr); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - EXPECT_FIND_("TRACE_EVENT_ASYNC_END2 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kAsyncIdStr); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - EXPECT_SUB_FIND_("name2"); - EXPECT_SUB_FIND_("value2"); - - EXPECT_FIND_("TRACE_EVENT_FLOW_BEGIN0 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kFlowIdStr); - EXPECT_FIND_("TRACE_EVENT_FLOW_STEP0 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kFlowIdStr); - EXPECT_SUB_FIND_("step1"); - EXPECT_FIND_("TRACE_EVENT_FLOW_END_BIND_TO_ENCLOSING0 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kFlowIdStr); - - EXPECT_FIND_("TRACE_COUNTER1 call"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("C", ph); - - int value; - EXPECT_TRUE((item && item->GetInteger("args.value", &value))); - EXPECT_EQ(31415, value); - } - - EXPECT_FIND_("TRACE_COUNTER2 call"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("C", ph); - - int value; - EXPECT_TRUE((item && item->GetInteger("args.a", &value))); - EXPECT_EQ(30000, value); - - EXPECT_TRUE((item && item->GetInteger("args.b", &value))); - EXPECT_EQ(1415, value); - } - - EXPECT_FIND_("TRACE_COUNTER_WITH_TIMESTAMP1 call"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("C", ph); - - int value; - EXPECT_TRUE((item && item->GetInteger("args.value", &value))); - EXPECT_EQ(31415, value); - - int ts; - EXPECT_TRUE((item && item->GetInteger("ts", &ts))); - EXPECT_EQ(42, ts); - } - - EXPECT_FIND_("TRACE_COUNTER_WITH_TIMESTAMP2 call"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("C", ph); - - int value; - EXPECT_TRUE((item && item->GetInteger("args.a", &value))); - EXPECT_EQ(30000, value); - - EXPECT_TRUE((item && item->GetInteger("args.b", &value))); - EXPECT_EQ(1415, value); - - int ts; - EXPECT_TRUE((item && item->GetInteger("ts", &ts))); - EXPECT_EQ(42, ts); - } - - EXPECT_FIND_("TRACE_COUNTER_ID1 call"); - { - std::string id; - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ("0x319009", id); - - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("C", ph); - - int value; - EXPECT_TRUE((item && item->GetInteger("args.value", &value))); - EXPECT_EQ(31415, value); - } - - EXPECT_FIND_("TRACE_COUNTER_ID2 call"); - { - std::string id; - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ("0x319009", id); - - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("C", ph); - - int value; - EXPECT_TRUE((item && item->GetInteger("args.a", &value))); - EXPECT_EQ(30000, value); - - EXPECT_TRUE((item && item->GetInteger("args.b", &value))); - EXPECT_EQ(1415, value); - } - - EXPECT_FIND_("TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call"); - { - int val; - EXPECT_TRUE((item && item->GetInteger("ts", &val))); - EXPECT_EQ(12345, val); - EXPECT_TRUE((item && item->GetInteger("tid", &val))); - EXPECT_EQ(kThreadId, val); - std::string id; - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ(kAsyncIdStr, id); - } - - EXPECT_FIND_("TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0 call"); - { - int val; - EXPECT_TRUE((item && item->GetInteger("ts", &val))); - EXPECT_EQ(23456, val); - EXPECT_TRUE((item && item->GetInteger("tid", &val))); - EXPECT_EQ(kThreadId, val); - std::string id; - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ(kAsyncIdStr, id); - } - - EXPECT_FIND_("TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call"); - { - int val; - EXPECT_TRUE((item && item->GetInteger("ts", &val))); - EXPECT_EQ(34567, val); - EXPECT_TRUE((item && item->GetInteger("tid", &val))); - EXPECT_EQ(kThreadId, val); - std::string id; - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ(kAsyncId2Str, id); - } - - EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_PAST0 call"); - { - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kAsyncId2Str); - EXPECT_SUB_FIND_("step_end1"); - EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_PAST1 call"); - EXPECT_SUB_FIND_("id"); - EXPECT_SUB_FIND_(kAsyncId2Str); - EXPECT_SUB_FIND_("step_end2"); - EXPECT_SUB_FIND_("name1"); - EXPECT_SUB_FIND_("value1"); - } - - EXPECT_FIND_("TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0 call"); - { - int val; - EXPECT_TRUE((item && item->GetInteger("ts", &val))); - EXPECT_EQ(45678, val); - EXPECT_TRUE((item && item->GetInteger("tid", &val))); - EXPECT_EQ(kThreadId, val); - std::string id; - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ(kAsyncId2Str, id); - } - - EXPECT_FIND_("tracked object 1"); - { - std::string phase; - std::string id; - std::string snapshot; - - EXPECT_TRUE((item && item->GetString("ph", &phase))); - EXPECT_EQ("N", phase); - EXPECT_FALSE((item && item->HasKey("scope"))); - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ("0x42", id); - - item = FindTraceEntry(trace_parsed, "tracked object 1", item); - EXPECT_TRUE(item); - EXPECT_TRUE(item && item->GetString("ph", &phase)); - EXPECT_EQ("O", phase); - EXPECT_FALSE((item && item->HasKey("scope"))); - EXPECT_TRUE(item && item->GetString("id", &id)); - EXPECT_EQ("0x42", id); - EXPECT_TRUE(item && item->GetString("args.snapshot", &snapshot)); - EXPECT_EQ("hello", snapshot); - - item = FindTraceEntry(trace_parsed, "tracked object 1", item); - EXPECT_TRUE(item); - EXPECT_TRUE(item && item->GetString("ph", &phase)); - EXPECT_EQ("D", phase); - EXPECT_FALSE((item && item->HasKey("scope"))); - EXPECT_TRUE(item && item->GetString("id", &id)); - EXPECT_EQ("0x42", id); - } - - EXPECT_FIND_("tracked object 2"); - { - std::string phase; - std::string id; - std::string snapshot; - - EXPECT_TRUE(item && item->GetString("ph", &phase)); - EXPECT_EQ("N", phase); - EXPECT_TRUE(item && item->GetString("id", &id)); - EXPECT_EQ("0x2128506", id); - - item = FindTraceEntry(trace_parsed, "tracked object 2", item); - EXPECT_TRUE(item); - EXPECT_TRUE(item && item->GetString("ph", &phase)); - EXPECT_EQ("O", phase); - EXPECT_TRUE(item && item->GetString("id", &id)); - EXPECT_EQ("0x2128506", id); - EXPECT_TRUE(item && item->GetString("args.snapshot", &snapshot)); - EXPECT_EQ("world", snapshot); - - item = FindTraceEntry(trace_parsed, "tracked object 2", item); - EXPECT_TRUE(item); - EXPECT_TRUE(item && item->GetString("ph", &phase)); - EXPECT_EQ("D", phase); - EXPECT_TRUE(item && item->GetString("id", &id)); - EXPECT_EQ("0x2128506", id); - } - - EXPECT_FIND_("tracked object 3"); - { - std::string phase; - std::string scope; - std::string id; - std::string snapshot; - - EXPECT_TRUE((item && item->GetString("ph", &phase))); - EXPECT_EQ("N", phase); - EXPECT_TRUE((item && item->GetString("scope", &scope))); - EXPECT_EQ("scope", scope); - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ("0x42", id); - - item = FindTraceEntry(trace_parsed, "tracked object 3", item); - EXPECT_TRUE(item); - EXPECT_TRUE(item && item->GetString("ph", &phase)); - EXPECT_EQ("O", phase); - EXPECT_TRUE((item && item->GetString("scope", &scope))); - EXPECT_EQ("scope", scope); - EXPECT_TRUE(item && item->GetString("id", &id)); - EXPECT_EQ("0x42", id); - EXPECT_TRUE(item && item->GetString("args.snapshot", &snapshot)); - EXPECT_EQ("hello", snapshot); - - item = FindTraceEntry(trace_parsed, "tracked object 3", item); - EXPECT_TRUE(item); - EXPECT_TRUE(item && item->GetString("ph", &phase)); - EXPECT_EQ("D", phase); - EXPECT_TRUE((item && item->GetString("scope", &scope))); - EXPECT_EQ("scope", scope); - EXPECT_TRUE(item && item->GetString("id", &id)); - EXPECT_EQ("0x42", id); - } - - EXPECT_FIND_(kControlCharacters); - EXPECT_SUB_FIND_(kControlCharacters); - - EXPECT_FIND_("TRACE_EVENT_ENTER_CONTEXT call"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("(", ph); - - std::string scope; - std::string id; - EXPECT_TRUE((item && item->GetString("scope", &scope))); - EXPECT_EQ("scope", scope); - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ("0x20151021", id); - } - - EXPECT_FIND_("TRACE_EVENT_LEAVE_CONTEXT call"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ(")", ph); - - std::string scope; - std::string id; - EXPECT_TRUE((item && item->GetString("scope", &scope))); - EXPECT_EQ("scope", scope); - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ("0x20151021", id); - } - - std::vector scoped_context_calls = - FindTraceEntries(trace_parsed, "TRACE_EVENT_SCOPED_CONTEXT call"); - EXPECT_EQ(2u, scoped_context_calls.size()); - { - item = scoped_context_calls[0]; - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("(", ph); - - std::string id; - EXPECT_FALSE((item && item->HasKey("scope"))); - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ("0x20151021", id); - } - - { - item = scoped_context_calls[1]; - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ(")", ph); - - std::string id; - EXPECT_FALSE((item && item->HasKey("scope"))); - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ("0x20151021", id); - } - - EXPECT_FIND_("TRACE_LINK_IDS simple call"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("=", ph); - - EXPECT_FALSE((item && item->HasKey("scope"))); - std::string id1; - EXPECT_TRUE((item && item->GetString("id", &id1))); - EXPECT_EQ("0x1000", id1); - - EXPECT_FALSE((item && item->HasKey("args.linked_id.scope"))); - std::string id2; - EXPECT_TRUE((item && item->GetString("args.linked_id.id", &id2))); - EXPECT_EQ("0x2000", id2); - } - - EXPECT_FIND_("TRACE_LINK_IDS scoped call"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("=", ph); - - std::string scope1; - EXPECT_TRUE((item && item->GetString("scope", &scope1))); - EXPECT_EQ("scope 1", scope1); - std::string id1; - EXPECT_TRUE((item && item->GetString("id", &id1))); - EXPECT_EQ("0x1000", id1); - - std::string scope2; - EXPECT_TRUE((item && item->GetString("args.linked_id.scope", &scope2))); - EXPECT_EQ("scope 2", scope2); - std::string id2; - EXPECT_TRUE((item && item->GetString("args.linked_id.id", &id2))); - EXPECT_EQ("0x2000", id2); - } - - EXPECT_FIND_("TRACE_LINK_IDS to a local ID"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("=", ph); - - EXPECT_FALSE((item && item->HasKey("scope"))); - std::string id1; - EXPECT_TRUE((item && item->GetString("id", &id1))); - EXPECT_EQ("0x1000", id1); - - EXPECT_FALSE((item && item->HasKey("args.linked_id.scope"))); - std::string id2; - EXPECT_TRUE((item && item->GetString("args.linked_id.id2.local", &id2))); - EXPECT_EQ("0x2000", id2); - } - - EXPECT_FIND_("TRACE_LINK_IDS to a global ID"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("=", ph); - - EXPECT_FALSE((item && item->HasKey("scope"))); - std::string id1; - EXPECT_TRUE((item && item->GetString("id", &id1))); - EXPECT_EQ("0x1000", id1); - - EXPECT_FALSE((item && item->HasKey("args.linked_id.scope"))); - std::string id2; - EXPECT_TRUE((item && item->GetString("args.linked_id.id2.global", &id2))); - EXPECT_EQ("0x2000", id2); - } - - EXPECT_FIND_("TRACE_LINK_IDS to a composite ID"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("=", ph); - - EXPECT_FALSE(item->HasKey("scope")); - std::string id1; - EXPECT_TRUE(item->GetString("id", &id1)); - EXPECT_EQ("0x1000", id1); - - std::string scope; - EXPECT_TRUE(item->GetString("args.linked_id.scope", &scope)); - EXPECT_EQ("scope 1", scope); - std::string id2; - EXPECT_TRUE(item->GetString("args.linked_id.id", &id2)); - EXPECT_EQ(id2, "0x2000/0x3000"); - } - - EXPECT_FIND_("async default process scope"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("S", ph); - - std::string id; - EXPECT_TRUE((item && item->GetString("id", &id))); - EXPECT_EQ("0x1000", id); - } - - EXPECT_FIND_("async local id"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("S", ph); - - std::string id; - EXPECT_TRUE((item && item->GetString("id2.local", &id))); - EXPECT_EQ("0x2000", id); - } - - EXPECT_FIND_("async global id"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("S", ph); - - std::string id; - EXPECT_TRUE((item && item->GetString("id2.global", &id))); - EXPECT_EQ("0x3000", id); - } - - EXPECT_FIND_("async global id with scope string"); - { - std::string ph; - EXPECT_TRUE((item && item->GetString("ph", &ph))); - EXPECT_EQ("S", ph); - - std::string id; - EXPECT_TRUE((item && item->GetString("id2.global", &id))); - EXPECT_EQ("0x4000", id); - std::string scope; - EXPECT_TRUE((item && item->GetString("scope", &scope))); - EXPECT_EQ("scope string", scope); - } -} - -void TraceManyInstantEvents(int thread_id, int num_events, - WaitableEvent* task_complete_event) { - for (int i = 0; i < num_events; i++) { - TRACE_EVENT_INSTANT2("all", "multi thread event", - TRACE_EVENT_SCOPE_THREAD, - "thread", thread_id, - "event", i); - } - - if (task_complete_event) - task_complete_event->Signal(); -} - -void ValidateInstantEventPresentOnEveryThread(const ListValue& trace_parsed, - int num_threads, - int num_events) { - std::map > results; - - size_t trace_parsed_count = trace_parsed.GetSize(); - for (size_t i = 0; i < trace_parsed_count; i++) { - const Value* value = NULL; - trace_parsed.Get(i, &value); - if (!value || value->GetType() != Value::Type::DICTIONARY) - continue; - const DictionaryValue* dict = static_cast(value); - std::string name; - dict->GetString("name", &name); - if (name != "multi thread event") - continue; - - int thread = 0; - int event = 0; - EXPECT_TRUE(dict->GetInteger("args.thread", &thread)); - EXPECT_TRUE(dict->GetInteger("args.event", &event)); - results[thread][event] = true; - } - - EXPECT_FALSE(results[-1][-1]); - for (int thread = 0; thread < num_threads; thread++) { - for (int event = 0; event < num_events; event++) { - EXPECT_TRUE(results[thread][event]); - } - } -} - -void CheckTraceDefaultCategoryFilters(const TraceLog& trace_log) { - // Default enables all category filters except the disabled-by-default-* ones. - EXPECT_TRUE(*trace_log.GetCategoryGroupEnabled("foo")); - EXPECT_TRUE(*trace_log.GetCategoryGroupEnabled("bar")); - EXPECT_TRUE(*trace_log.GetCategoryGroupEnabled("foo,bar")); - EXPECT_TRUE(*trace_log.GetCategoryGroupEnabled( - "foo,disabled-by-default-foo")); - EXPECT_FALSE(*trace_log.GetCategoryGroupEnabled( - "disabled-by-default-foo,disabled-by-default-bar")); -} - -} // namespace - -// Simple Test for emitting data and validating it was received. -TEST_F(TraceEventTestFixture, DataCaptured) { - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - - TraceWithAllMacroVariants(NULL); - - EndTraceAndFlush(); - - ValidateAllTraceMacrosCreatedData(trace_parsed_); -} - -// Emit some events and validate that only empty strings are received -// if we tell Flush() to discard events. -TEST_F(TraceEventTestFixture, DataDiscarded) { - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - - TraceWithAllMacroVariants(NULL); - - CancelTrace(); - - EXPECT_TRUE(trace_parsed_.empty()); -} - -class MockEnabledStateChangedObserver : - public TraceLog::EnabledStateObserver { - public: - MOCK_METHOD0(OnTraceLogEnabled, void()); - MOCK_METHOD0(OnTraceLogDisabled, void()); -}; - -TEST_F(TraceEventTestFixture, EnabledObserverFiresOnEnable) { - MockEnabledStateChangedObserver observer; - TraceLog::GetInstance()->AddEnabledStateObserver(&observer); - - EXPECT_CALL(observer, OnTraceLogEnabled()) - .Times(1); - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - testing::Mock::VerifyAndClear(&observer); - EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled()); - - // Cleanup. - TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); - TraceLog::GetInstance()->SetDisabled(); -} - -TEST_F(TraceEventTestFixture, EnabledObserverDoesntFireOnSecondEnable) { - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - - testing::StrictMock observer; - TraceLog::GetInstance()->AddEnabledStateObserver(&observer); - - EXPECT_CALL(observer, OnTraceLogEnabled()) - .Times(0); - EXPECT_CALL(observer, OnTraceLogDisabled()) - .Times(0); - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - testing::Mock::VerifyAndClear(&observer); - EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled()); - - // Cleanup. - TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); - TraceLog::GetInstance()->SetDisabled(); - TraceLog::GetInstance()->SetDisabled(); -} - -TEST_F(TraceEventTestFixture, EnabledObserverFiresOnFirstDisable) { - TraceConfig tc_inc_all("*", ""); - TraceLog::GetInstance()->SetEnabled(tc_inc_all, TraceLog::RECORDING_MODE); - TraceLog::GetInstance()->SetEnabled(tc_inc_all, TraceLog::RECORDING_MODE); - - testing::StrictMock observer; - TraceLog::GetInstance()->AddEnabledStateObserver(&observer); - - EXPECT_CALL(observer, OnTraceLogEnabled()) - .Times(0); - EXPECT_CALL(observer, OnTraceLogDisabled()) - .Times(1); - TraceLog::GetInstance()->SetDisabled(); - testing::Mock::VerifyAndClear(&observer); - - // Cleanup. - TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); - TraceLog::GetInstance()->SetDisabled(); -} - -TEST_F(TraceEventTestFixture, EnabledObserverFiresOnDisable) { - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - - MockEnabledStateChangedObserver observer; - TraceLog::GetInstance()->AddEnabledStateObserver(&observer); - - EXPECT_CALL(observer, OnTraceLogDisabled()) - .Times(1); - TraceLog::GetInstance()->SetDisabled(); - testing::Mock::VerifyAndClear(&observer); - - // Cleanup. - TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); -} - -// Tests the IsEnabled() state of TraceLog changes before callbacks. -class AfterStateChangeEnabledStateObserver - : public TraceLog::EnabledStateObserver { - public: - AfterStateChangeEnabledStateObserver() {} - ~AfterStateChangeEnabledStateObserver() override {} - - // TraceLog::EnabledStateObserver overrides: - void OnTraceLogEnabled() override { - EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled()); - } - - void OnTraceLogDisabled() override { - EXPECT_FALSE(TraceLog::GetInstance()->IsEnabled()); - } -}; - -TEST_F(TraceEventTestFixture, ObserversFireAfterStateChange) { - AfterStateChangeEnabledStateObserver observer; - TraceLog::GetInstance()->AddEnabledStateObserver(&observer); - - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled()); - - TraceLog::GetInstance()->SetDisabled(); - EXPECT_FALSE(TraceLog::GetInstance()->IsEnabled()); - - TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); -} - -// Tests that a state observer can remove itself during a callback. -class SelfRemovingEnabledStateObserver - : public TraceLog::EnabledStateObserver { - public: - SelfRemovingEnabledStateObserver() {} - ~SelfRemovingEnabledStateObserver() override {} - - // TraceLog::EnabledStateObserver overrides: - void OnTraceLogEnabled() override {} - - void OnTraceLogDisabled() override { - TraceLog::GetInstance()->RemoveEnabledStateObserver(this); - } -}; - -TEST_F(TraceEventTestFixture, SelfRemovingObserver) { - ASSERT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest()); - - SelfRemovingEnabledStateObserver observer; - TraceLog::GetInstance()->AddEnabledStateObserver(&observer); - EXPECT_EQ(1u, TraceLog::GetInstance()->GetObserverCountForTest()); - - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - TraceLog::GetInstance()->SetDisabled(); - // The observer removed itself on disable. - EXPECT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest()); -} - -bool IsNewTrace() { - bool is_new_trace; - TRACE_EVENT_IS_NEW_TRACE(&is_new_trace); - return is_new_trace; -} - -TEST_F(TraceEventTestFixture, NewTraceRecording) { - ASSERT_FALSE(IsNewTrace()); - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - // First call to IsNewTrace() should succeed. But, the second shouldn't. - ASSERT_TRUE(IsNewTrace()); - ASSERT_FALSE(IsNewTrace()); - EndTraceAndFlush(); - - // IsNewTrace() should definitely be false now. - ASSERT_FALSE(IsNewTrace()); - - // Start another trace. IsNewTrace() should become true again, briefly, as - // before. - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - ASSERT_TRUE(IsNewTrace()); - ASSERT_FALSE(IsNewTrace()); - - // Cleanup. - EndTraceAndFlush(); -} - -TEST_F(TraceEventTestFixture, TestTraceFlush) { - size_t min_traces = 1; - size_t max_traces = 1; - do { - max_traces *= 2; - TraceLog::GetInstance()->SetEnabled(TraceConfig(), - TraceLog::RECORDING_MODE); - for (size_t i = 0; i < max_traces; i++) { - TRACE_EVENT_INSTANT0("x", "y", TRACE_EVENT_SCOPE_THREAD); - } - EndTraceAndFlush(); - } while (num_flush_callbacks_ < 2); - - while (min_traces + 50 < max_traces) { - size_t traces = (min_traces + max_traces) / 2; - TraceLog::GetInstance()->SetEnabled(TraceConfig(), - TraceLog::RECORDING_MODE); - for (size_t i = 0; i < traces; i++) { - TRACE_EVENT_INSTANT0("x", "y", TRACE_EVENT_SCOPE_THREAD); - } - EndTraceAndFlush(); - if (num_flush_callbacks_ < 2) { - min_traces = traces - 10; - } else { - max_traces = traces + 10; - } - } - - for (size_t traces = min_traces; traces < max_traces; traces++) { - TraceLog::GetInstance()->SetEnabled(TraceConfig(), - TraceLog::RECORDING_MODE); - for (size_t i = 0; i < traces; i++) { - TRACE_EVENT_INSTANT0("x", "y", TRACE_EVENT_SCOPE_THREAD); - } - EndTraceAndFlush(); - } -} - -TEST_F(TraceEventTestFixture, AddMetadataEvent) { - int num_calls = 0; - - class Convertable : public ConvertableToTraceFormat { - public: - explicit Convertable(int* num_calls) : num_calls_(num_calls) {} - ~Convertable() override {} - void AppendAsTraceFormat(std::string* out) const override { - (*num_calls_)++; - out->append("\"metadata_value\""); - } - - private: - int* num_calls_; - }; - - std::unique_ptr conv1(new Convertable(&num_calls)); - std::unique_ptr conv2(new Convertable(&num_calls)); - - BeginTrace(); - TRACE_EVENT_API_ADD_METADATA_EVENT( - TraceLog::GetCategoryGroupEnabled("__metadata"), "metadata_event_1", - "metadata_arg_name", std::move(conv1)); - TRACE_EVENT_API_ADD_METADATA_EVENT( - TraceLog::GetCategoryGroupEnabled("__metadata"), "metadata_event_2", - "metadata_arg_name", std::move(conv2)); - // |AppendAsTraceFormat| should only be called on flush, not when the event - // is added. - ASSERT_EQ(0, num_calls); - EndTraceAndFlush(); - ASSERT_EQ(2, num_calls); - EXPECT_TRUE(FindNamePhaseKeyValue("metadata_event_1", "M", - "metadata_arg_name", "metadata_value")); - EXPECT_TRUE(FindNamePhaseKeyValue("metadata_event_2", "M", - "metadata_arg_name", "metadata_value")); - - // The metadata event should only be adde to the current trace. In this new - // trace, the event should not appear. - BeginTrace(); - EndTraceAndFlush(); - ASSERT_EQ(2, num_calls); -} - -// Test that categories work. -TEST_F(TraceEventTestFixture, Categories) { - // Test that categories that are used can be retrieved whether trace was - // enabled or disabled when the trace event was encountered. - TRACE_EVENT_INSTANT0("c1", "name", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("c2", "name", TRACE_EVENT_SCOPE_THREAD); - BeginTrace(); - TRACE_EVENT_INSTANT0("c3", "name", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("c4", "name", TRACE_EVENT_SCOPE_THREAD); - // Category groups containing more than one category. - TRACE_EVENT_INSTANT0("c5,c6", "name", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("c7,c8", "name", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("c9"), "name", - TRACE_EVENT_SCOPE_THREAD); - - EndTraceAndFlush(); - std::vector cat_groups; - TraceLog::GetInstance()->GetKnownCategoryGroups(&cat_groups); - EXPECT_TRUE(ContainsValue(cat_groups, "c1")); - EXPECT_TRUE(ContainsValue(cat_groups, "c2")); - EXPECT_TRUE(ContainsValue(cat_groups, "c3")); - EXPECT_TRUE(ContainsValue(cat_groups, "c4")); - EXPECT_TRUE(ContainsValue(cat_groups, "c5,c6")); - EXPECT_TRUE(ContainsValue(cat_groups, "c7,c8")); - EXPECT_TRUE(ContainsValue(cat_groups, "disabled-by-default-c9")); - // Make sure metadata isn't returned. - EXPECT_FALSE(ContainsValue(cat_groups, "__metadata")); - - const std::vector empty_categories; - std::vector included_categories; - std::vector excluded_categories; - - // Test that category filtering works. - - // Include nonexistent category -> no events - Clear(); - included_categories.clear(); - TraceLog::GetInstance()->SetEnabled(TraceConfig("not_found823564786", ""), - TraceLog::RECORDING_MODE); - TRACE_EVENT_INSTANT0("cat1", "name", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("cat2", "name", TRACE_EVENT_SCOPE_THREAD); - EndTraceAndFlush(); - DropTracedMetadataRecords(); - EXPECT_TRUE(trace_parsed_.empty()); - - // Include existent category -> only events of that category - Clear(); - included_categories.clear(); - TraceLog::GetInstance()->SetEnabled(TraceConfig("inc", ""), - TraceLog::RECORDING_MODE); - TRACE_EVENT_INSTANT0("inc", "name", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("inc2", "name", TRACE_EVENT_SCOPE_THREAD); - EndTraceAndFlush(); - DropTracedMetadataRecords(); - EXPECT_TRUE(FindMatchingValue("cat", "inc")); - EXPECT_FALSE(FindNonMatchingValue("cat", "inc")); - - // Include existent wildcard -> all categories matching wildcard - Clear(); - included_categories.clear(); - TraceLog::GetInstance()->SetEnabled( - TraceConfig("inc_wildcard_*,inc_wildchar_?_end", ""), - TraceLog::RECORDING_MODE); - TRACE_EVENT_INSTANT0("inc_wildcard_abc", "included", - TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("inc_wildcard_", "included", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("inc_wildchar_x_end", "included", - TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("inc_wildchar_bla_end", "not_inc", - TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("cat1", "not_inc", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("cat2", "not_inc", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("inc_wildcard_category,other_category", "included", - TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0( - "non_included_category,inc_wildcard_category", "included", - TRACE_EVENT_SCOPE_THREAD); - EndTraceAndFlush(); - EXPECT_TRUE(FindMatchingValue("cat", "inc_wildcard_abc")); - EXPECT_TRUE(FindMatchingValue("cat", "inc_wildcard_")); - EXPECT_TRUE(FindMatchingValue("cat", "inc_wildchar_x_end")); - EXPECT_FALSE(FindMatchingValue("name", "not_inc")); - EXPECT_TRUE(FindMatchingValue("cat", "inc_wildcard_category,other_category")); - EXPECT_TRUE(FindMatchingValue("cat", - "non_included_category,inc_wildcard_category")); - - included_categories.clear(); - - // Exclude nonexistent category -> all events - Clear(); - TraceLog::GetInstance()->SetEnabled(TraceConfig("-not_found823564786", ""), - TraceLog::RECORDING_MODE); - TRACE_EVENT_INSTANT0("cat1", "name", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("cat2", "name", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("category1,category2", "name", TRACE_EVENT_SCOPE_THREAD); - EndTraceAndFlush(); - EXPECT_TRUE(FindMatchingValue("cat", "cat1")); - EXPECT_TRUE(FindMatchingValue("cat", "cat2")); - EXPECT_TRUE(FindMatchingValue("cat", "category1,category2")); - - // Exclude existent category -> only events of other categories - Clear(); - TraceLog::GetInstance()->SetEnabled(TraceConfig("-inc", ""), - TraceLog::RECORDING_MODE); - TRACE_EVENT_INSTANT0("inc", "name", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("inc2", "name", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("inc2,inc", "name", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("inc,inc2", "name", TRACE_EVENT_SCOPE_THREAD); - EndTraceAndFlush(); - EXPECT_TRUE(FindMatchingValue("cat", "inc2")); - EXPECT_FALSE(FindMatchingValue("cat", "inc")); - EXPECT_TRUE(FindMatchingValue("cat", "inc2,inc")); - EXPECT_TRUE(FindMatchingValue("cat", "inc,inc2")); - - // Exclude existent wildcard -> all categories not matching wildcard - Clear(); - TraceLog::GetInstance()->SetEnabled( - TraceConfig("-inc_wildcard_*,-inc_wildchar_?_end", ""), - TraceLog::RECORDING_MODE); - TRACE_EVENT_INSTANT0("inc_wildcard_abc", "not_inc", - TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("inc_wildcard_", "not_inc", - TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("inc_wildchar_x_end", "not_inc", - TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("inc_wildchar_bla_end", "included", - TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("cat1", "included", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("cat2", "included", TRACE_EVENT_SCOPE_THREAD); - EndTraceAndFlush(); - EXPECT_TRUE(FindMatchingValue("cat", "inc_wildchar_bla_end")); - EXPECT_TRUE(FindMatchingValue("cat", "cat1")); - EXPECT_TRUE(FindMatchingValue("cat", "cat2")); - EXPECT_FALSE(FindMatchingValue("name", "not_inc")); -} - - -// Test ASYNC_BEGIN/END events -TEST_F(TraceEventTestFixture, AsyncBeginEndEvents) { - BeginTrace(); - - unsigned long long id = 0xfeedbeeffeedbeefull; - TRACE_EVENT_ASYNC_BEGIN0("cat", "name1", id); - TRACE_EVENT_ASYNC_STEP_INTO0("cat", "name1", id, "step1"); - TRACE_EVENT_ASYNC_END0("cat", "name1", id); - TRACE_EVENT_BEGIN0("cat", "name2"); - TRACE_EVENT_ASYNC_BEGIN0("cat", "name3", 0); - TRACE_EVENT_ASYNC_STEP_PAST0("cat", "name3", 0, "step2"); - - EndTraceAndFlush(); - - EXPECT_TRUE(FindNamePhase("name1", "S")); - EXPECT_TRUE(FindNamePhase("name1", "T")); - EXPECT_TRUE(FindNamePhase("name1", "F")); - - std::string id_str; - StringAppendF(&id_str, "0x%llx", id); - - EXPECT_TRUE(FindNamePhaseKeyValue("name1", "S", "id", id_str.c_str())); - EXPECT_TRUE(FindNamePhaseKeyValue("name1", "T", "id", id_str.c_str())); - EXPECT_TRUE(FindNamePhaseKeyValue("name1", "F", "id", id_str.c_str())); - EXPECT_TRUE(FindNamePhaseKeyValue("name3", "S", "id", "0x0")); - EXPECT_TRUE(FindNamePhaseKeyValue("name3", "p", "id", "0x0")); - - // BEGIN events should not have id - EXPECT_FALSE(FindNamePhaseKeyValue("name2", "B", "id", "0")); -} - -// Test ASYNC_BEGIN/END events -TEST_F(TraceEventTestFixture, AsyncBeginEndPointerMangling) { - void* ptr = this; - - TraceLog::GetInstance()->SetProcessID(100); - BeginTrace(); - TRACE_EVENT_ASYNC_BEGIN0("cat", "name1", ptr); - TRACE_EVENT_ASYNC_BEGIN0("cat", "name2", ptr); - EndTraceAndFlush(); - - TraceLog::GetInstance()->SetProcessID(200); - BeginTrace(); - TRACE_EVENT_ASYNC_END0("cat", "name1", ptr); - EndTraceAndFlush(); - - DictionaryValue* async_begin = FindNamePhase("name1", "S"); - DictionaryValue* async_begin2 = FindNamePhase("name2", "S"); - DictionaryValue* async_end = FindNamePhase("name1", "F"); - EXPECT_TRUE(async_begin); - EXPECT_TRUE(async_begin2); - EXPECT_TRUE(async_end); - - Value* value = NULL; - std::string async_begin_id_str; - std::string async_begin2_id_str; - std::string async_end_id_str; - ASSERT_TRUE(async_begin->Get("id", &value)); - ASSERT_TRUE(value->GetAsString(&async_begin_id_str)); - ASSERT_TRUE(async_begin2->Get("id", &value)); - ASSERT_TRUE(value->GetAsString(&async_begin2_id_str)); - ASSERT_TRUE(async_end->Get("id", &value)); - ASSERT_TRUE(value->GetAsString(&async_end_id_str)); - - EXPECT_STREQ(async_begin_id_str.c_str(), async_begin2_id_str.c_str()); - EXPECT_STRNE(async_begin_id_str.c_str(), async_end_id_str.c_str()); -} - -// Test that static strings are not copied. -TEST_F(TraceEventTestFixture, StaticStringVsString) { - TraceLog* tracer = TraceLog::GetInstance(); - // Make sure old events are flushed: - EXPECT_EQ(0u, tracer->GetStatus().event_count); - const unsigned char* category_group_enabled = - TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("cat"); - - { - BeginTrace(); - // Test that string arguments are copied. - TraceEventHandle handle1 = - trace_event_internal::AddTraceEvent( - TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name1", - trace_event_internal::kGlobalScope, trace_event_internal::kNoId, - 0, trace_event_internal::kNoId, - "arg1", std::string("argval"), "arg2", std::string("argval")); - // Test that static TRACE_STR_COPY string arguments are copied. - TraceEventHandle handle2 = - trace_event_internal::AddTraceEvent( - TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name2", - trace_event_internal::kGlobalScope, trace_event_internal::kNoId, - 0, trace_event_internal::kNoId, - "arg1", TRACE_STR_COPY("argval"), - "arg2", TRACE_STR_COPY("argval")); - EXPECT_GT(tracer->GetStatus().event_count, 1u); - const TraceEvent* event1 = tracer->GetEventByHandle(handle1); - const TraceEvent* event2 = tracer->GetEventByHandle(handle2); - ASSERT_TRUE(event1); - ASSERT_TRUE(event2); - EXPECT_STREQ("name1", event1->name()); - EXPECT_STREQ("name2", event2->name()); - EXPECT_TRUE(event1->parameter_copy_storage() != NULL); - EXPECT_TRUE(event2->parameter_copy_storage() != NULL); - EXPECT_GT(event1->parameter_copy_storage()->size(), 0u); - EXPECT_GT(event2->parameter_copy_storage()->size(), 0u); - EndTraceAndFlush(); - } - - { - BeginTrace(); - // Test that static literal string arguments are not copied. - TraceEventHandle handle1 = - trace_event_internal::AddTraceEvent( - TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name1", - trace_event_internal::kGlobalScope, trace_event_internal::kNoId, - 0, trace_event_internal::kNoId, - "arg1", "argval", "arg2", "argval"); - // Test that static TRACE_STR_COPY NULL string arguments are not copied. - const char* str1 = NULL; - const char* str2 = NULL; - TraceEventHandle handle2 = - trace_event_internal::AddTraceEvent( - TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name2", - trace_event_internal::kGlobalScope, trace_event_internal::kNoId, - 0, trace_event_internal::kNoId, - "arg1", TRACE_STR_COPY(str1), - "arg2", TRACE_STR_COPY(str2)); - EXPECT_GT(tracer->GetStatus().event_count, 1u); - const TraceEvent* event1 = tracer->GetEventByHandle(handle1); - const TraceEvent* event2 = tracer->GetEventByHandle(handle2); - ASSERT_TRUE(event1); - ASSERT_TRUE(event2); - EXPECT_STREQ("name1", event1->name()); - EXPECT_STREQ("name2", event2->name()); - EXPECT_TRUE(event1->parameter_copy_storage() == NULL); - EXPECT_TRUE(event2->parameter_copy_storage() == NULL); - EndTraceAndFlush(); - } -} - -// Test that data sent from other threads is gathered -TEST_F(TraceEventTestFixture, DataCapturedOnThread) { - BeginTrace(); - - Thread thread("1"); - WaitableEvent task_complete_event(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - thread.Start(); - - thread.task_runner()->PostTask( - FROM_HERE, base::Bind(&TraceWithAllMacroVariants, &task_complete_event)); - task_complete_event.Wait(); - thread.Stop(); - - EndTraceAndFlush(); - ValidateAllTraceMacrosCreatedData(trace_parsed_); -} - -// Test that data sent from multiple threads is gathered -TEST_F(TraceEventTestFixture, DataCapturedManyThreads) { - BeginTrace(); - - const int num_threads = 4; - const int num_events = 4000; - Thread* threads[num_threads]; - WaitableEvent* task_complete_events[num_threads]; - for (int i = 0; i < num_threads; i++) { - threads[i] = new Thread(StringPrintf("Thread %d", i)); - task_complete_events[i] = - new WaitableEvent(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - threads[i]->Start(); - threads[i]->task_runner()->PostTask( - FROM_HERE, base::Bind(&TraceManyInstantEvents, i, num_events, - task_complete_events[i])); - } - - for (int i = 0; i < num_threads; i++) { - task_complete_events[i]->Wait(); - } - - // Let half of the threads end before flush. - for (int i = 0; i < num_threads / 2; i++) { - threads[i]->Stop(); - delete threads[i]; - delete task_complete_events[i]; - } - - EndTraceAndFlushInThreadWithMessageLoop(); - ValidateInstantEventPresentOnEveryThread(trace_parsed_, - num_threads, num_events); - - // Let the other half of the threads end after flush. - for (int i = num_threads / 2; i < num_threads; i++) { - threads[i]->Stop(); - delete threads[i]; - delete task_complete_events[i]; - } -} - -// Test that thread and process names show up in the trace -TEST_F(TraceEventTestFixture, ThreadNames) { - // Create threads before we enable tracing to make sure - // that tracelog still captures them. - const int kNumThreads = 4; - const int kNumEvents = 10; - Thread* threads[kNumThreads]; - PlatformThreadId thread_ids[kNumThreads]; - for (int i = 0; i < kNumThreads; i++) - threads[i] = new Thread(StringPrintf("Thread %d", i)); - - // Enable tracing. - BeginTrace(); - - // Now run some trace code on these threads. - WaitableEvent* task_complete_events[kNumThreads]; - for (int i = 0; i < kNumThreads; i++) { - task_complete_events[i] = - new WaitableEvent(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - threads[i]->Start(); - thread_ids[i] = threads[i]->GetThreadId(); - threads[i]->task_runner()->PostTask( - FROM_HERE, base::Bind(&TraceManyInstantEvents, i, kNumEvents, - task_complete_events[i])); - } - for (int i = 0; i < kNumThreads; i++) { - task_complete_events[i]->Wait(); - } - - // Shut things down. - for (int i = 0; i < kNumThreads; i++) { - threads[i]->Stop(); - delete threads[i]; - delete task_complete_events[i]; - } - - EndTraceAndFlush(); - - std::string tmp; - int tmp_int; - const DictionaryValue* item; - - // Make sure we get thread name metadata. - // Note, the test suite may have created a ton of threads. - // So, we'll have thread names for threads we didn't create. - std::vector items = - FindTraceEntries(trace_parsed_, "thread_name"); - for (int i = 0; i < static_cast(items.size()); i++) { - item = items[i]; - ASSERT_TRUE(item); - EXPECT_TRUE(item->GetInteger("tid", &tmp_int)); - - // See if this thread name is one of the threads we just created - for (int j = 0; j < kNumThreads; j++) { - if (static_cast(thread_ids[j]) != tmp_int) - continue; - - std::string expected_name = StringPrintf("Thread %d", j); - EXPECT_TRUE(item->GetString("ph", &tmp) && tmp == "M"); - EXPECT_TRUE(item->GetInteger("pid", &tmp_int) && - tmp_int == static_cast(base::GetCurrentProcId())); - // If the thread name changes or the tid gets reused, the name will be - // a comma-separated list of thread names, so look for a substring. - EXPECT_TRUE(item->GetString("args.name", &tmp) && - tmp.find(expected_name) != std::string::npos); - } - } -} - -TEST_F(TraceEventTestFixture, ThreadNameChanges) { - BeginTrace(); - - PlatformThread::SetName(""); - TRACE_EVENT_INSTANT0("drink", "water", TRACE_EVENT_SCOPE_THREAD); - - PlatformThread::SetName("cafe"); - TRACE_EVENT_INSTANT0("drink", "coffee", TRACE_EVENT_SCOPE_THREAD); - - PlatformThread::SetName("shop"); - // No event here, so won't appear in combined name. - - PlatformThread::SetName("pub"); - TRACE_EVENT_INSTANT0("drink", "beer", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("drink", "wine", TRACE_EVENT_SCOPE_THREAD); - - PlatformThread::SetName(" bar"); - TRACE_EVENT_INSTANT0("drink", "whisky", TRACE_EVENT_SCOPE_THREAD); - - EndTraceAndFlush(); - - std::vector items = - FindTraceEntries(trace_parsed_, "thread_name"); - EXPECT_EQ(1u, items.size()); - ASSERT_GT(items.size(), 0u); - const DictionaryValue* item = items[0]; - ASSERT_TRUE(item); - int tid; - EXPECT_TRUE(item->GetInteger("tid", &tid)); - EXPECT_EQ(PlatformThread::CurrentId(), static_cast(tid)); - - std::string expected_name = "cafe,pub, bar"; - std::string tmp; - EXPECT_TRUE(item->GetString("args.name", &tmp)); - EXPECT_EQ(expected_name, tmp); -} - -// Test that the disabled trace categories are included/excluded from the -// trace output correctly. -TEST_F(TraceEventTestFixture, DisabledCategories) { - BeginTrace(); - TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("cc"), "first", - TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("included", "first", TRACE_EVENT_SCOPE_THREAD); - EndTraceAndFlush(); - { - const DictionaryValue* item = NULL; - ListValue& trace_parsed = trace_parsed_; - EXPECT_NOT_FIND_("disabled-by-default-cc"); - EXPECT_FIND_("included"); - } - Clear(); - - BeginSpecificTrace("disabled-by-default-cc"); - TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("cc"), "second", - TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("other_included", "second", TRACE_EVENT_SCOPE_THREAD); - EndTraceAndFlush(); - - { - const DictionaryValue* item = NULL; - ListValue& trace_parsed = trace_parsed_; - EXPECT_FIND_("disabled-by-default-cc"); - EXPECT_FIND_("other_included"); - } - - Clear(); - - BeginSpecificTrace("other_included"); - TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("cc") ",other_included", - "first", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_INSTANT0("other_included," TRACE_DISABLED_BY_DEFAULT("cc"), - "second", TRACE_EVENT_SCOPE_THREAD); - EndTraceAndFlush(); - - { - const DictionaryValue* item = NULL; - ListValue& trace_parsed = trace_parsed_; - EXPECT_FIND_("disabled-by-default-cc,other_included"); - EXPECT_FIND_("other_included,disabled-by-default-cc"); - } -} - -TEST_F(TraceEventTestFixture, NormallyNoDeepCopy) { - // Test that the TRACE_EVENT macros do not deep-copy their string. If they - // do so it may indicate a performance regression, but more-over it would - // make the DEEP_COPY overloads redundant. - std::string name_string("event name"); - - BeginTrace(); - TRACE_EVENT_INSTANT0("category", name_string.c_str(), - TRACE_EVENT_SCOPE_THREAD); - - // Modify the string in place (a wholesale reassignment may leave the old - // string intact on the heap). - name_string[0] = '@'; - - EndTraceAndFlush(); - - EXPECT_FALSE(FindTraceEntry(trace_parsed_, "event name")); - EXPECT_TRUE(FindTraceEntry(trace_parsed_, name_string.c_str())); -} - -TEST_F(TraceEventTestFixture, DeepCopy) { - static const char kOriginalName1[] = "name1"; - static const char kOriginalName2[] = "name2"; - static const char kOriginalName3[] = "name3"; - std::string name1(kOriginalName1); - std::string name2(kOriginalName2); - std::string name3(kOriginalName3); - std::string arg1("arg1"); - std::string arg2("arg2"); - std::string val1("val1"); - std::string val2("val2"); - - BeginTrace(); - TRACE_EVENT_COPY_INSTANT0("category", name1.c_str(), - TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_COPY_BEGIN1("category", name2.c_str(), - arg1.c_str(), 5); - TRACE_EVENT_COPY_END2("category", name3.c_str(), - arg1.c_str(), val1, - arg2.c_str(), val2); - - // As per NormallyNoDeepCopy, modify the strings in place. - name1[0] = name2[0] = name3[0] = arg1[0] = arg2[0] = val1[0] = val2[0] = '@'; - - EndTraceAndFlush(); - - EXPECT_FALSE(FindTraceEntry(trace_parsed_, name1.c_str())); - EXPECT_FALSE(FindTraceEntry(trace_parsed_, name2.c_str())); - EXPECT_FALSE(FindTraceEntry(trace_parsed_, name3.c_str())); - - const DictionaryValue* entry1 = FindTraceEntry(trace_parsed_, kOriginalName1); - const DictionaryValue* entry2 = FindTraceEntry(trace_parsed_, kOriginalName2); - const DictionaryValue* entry3 = FindTraceEntry(trace_parsed_, kOriginalName3); - ASSERT_TRUE(entry1); - ASSERT_TRUE(entry2); - ASSERT_TRUE(entry3); - - int i; - EXPECT_FALSE(entry2->GetInteger("args.@rg1", &i)); - EXPECT_TRUE(entry2->GetInteger("args.arg1", &i)); - EXPECT_EQ(5, i); - - std::string s; - EXPECT_TRUE(entry3->GetString("args.arg1", &s)); - EXPECT_EQ("val1", s); - EXPECT_TRUE(entry3->GetString("args.arg2", &s)); - EXPECT_EQ("val2", s); -} - -// Test that TraceResultBuffer outputs the correct result whether it is added -// in chunks or added all at once. -TEST_F(TraceEventTestFixture, TraceResultBuffer) { - Clear(); - - trace_buffer_.Start(); - trace_buffer_.AddFragment("bla1"); - trace_buffer_.AddFragment("bla2"); - trace_buffer_.AddFragment("bla3,bla4"); - trace_buffer_.Finish(); - EXPECT_STREQ(json_output_.json_output.c_str(), "[bla1,bla2,bla3,bla4]"); - - Clear(); - - trace_buffer_.Start(); - trace_buffer_.AddFragment("bla1,bla2,bla3,bla4"); - trace_buffer_.Finish(); - EXPECT_STREQ(json_output_.json_output.c_str(), "[bla1,bla2,bla3,bla4]"); -} - -// Test that trace_event parameters are not evaluated if the tracing -// system is disabled. -TEST_F(TraceEventTestFixture, TracingIsLazy) { - BeginTrace(); - - int a = 0; - TRACE_EVENT_INSTANT1("category", "test", TRACE_EVENT_SCOPE_THREAD, "a", a++); - EXPECT_EQ(1, a); - - TraceLog::GetInstance()->SetDisabled(); - - TRACE_EVENT_INSTANT1("category", "test", TRACE_EVENT_SCOPE_THREAD, "a", a++); - EXPECT_EQ(1, a); - - EndTraceAndFlush(); -} - -TEST_F(TraceEventTestFixture, TraceEnableDisable) { - TraceLog* trace_log = TraceLog::GetInstance(); - TraceConfig tc_inc_all("*", ""); - trace_log->SetEnabled(tc_inc_all, TraceLog::RECORDING_MODE); - EXPECT_TRUE(trace_log->IsEnabled()); - trace_log->SetDisabled(); - EXPECT_FALSE(trace_log->IsEnabled()); - - trace_log->SetEnabled(tc_inc_all, TraceLog::RECORDING_MODE); - EXPECT_TRUE(trace_log->IsEnabled()); - const std::vector empty; - trace_log->SetEnabled(TraceConfig(), TraceLog::RECORDING_MODE); - EXPECT_TRUE(trace_log->IsEnabled()); - trace_log->SetDisabled(); - EXPECT_FALSE(trace_log->IsEnabled()); - trace_log->SetDisabled(); - EXPECT_FALSE(trace_log->IsEnabled()); -} - -TEST_F(TraceEventTestFixture, TraceCategoriesAfterNestedEnable) { - TraceLog* trace_log = TraceLog::GetInstance(); - trace_log->SetEnabled(TraceConfig("foo,bar", ""), TraceLog::RECORDING_MODE); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo")); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("bar")); - EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("baz")); - trace_log->SetEnabled(TraceConfig("foo2", ""), TraceLog::RECORDING_MODE); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo2")); - EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("baz")); - // The "" becomes the default catergory set when applied. - trace_log->SetEnabled(TraceConfig(), TraceLog::RECORDING_MODE); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo")); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("baz")); - EXPECT_STREQ( - "", - trace_log->GetCurrentTraceConfig().ToCategoryFilterString().c_str()); - trace_log->SetDisabled(); - trace_log->SetDisabled(); - trace_log->SetDisabled(); - EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("foo")); - EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("baz")); - - trace_log->SetEnabled(TraceConfig("-foo,-bar", ""), TraceLog::RECORDING_MODE); - EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("foo")); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("baz")); - trace_log->SetEnabled(TraceConfig("moo", ""), TraceLog::RECORDING_MODE); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("baz")); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("moo")); - EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("foo")); - EXPECT_STREQ( - "-foo,-bar", - trace_log->GetCurrentTraceConfig().ToCategoryFilterString().c_str()); - trace_log->SetDisabled(); - trace_log->SetDisabled(); - - // Make sure disabled categories aren't cleared if we set in the second. - trace_log->SetEnabled(TraceConfig("disabled-by-default-cc,foo", ""), - TraceLog::RECORDING_MODE); - EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("bar")); - trace_log->SetEnabled(TraceConfig("disabled-by-default-gpu", ""), - TraceLog::RECORDING_MODE); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-cc")); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-gpu")); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("bar")); - EXPECT_STREQ( - "disabled-by-default-cc,disabled-by-default-gpu", - trace_log->GetCurrentTraceConfig().ToCategoryFilterString().c_str()); - trace_log->SetDisabled(); - trace_log->SetDisabled(); -} - -TEST_F(TraceEventTestFixture, TraceWithDefaultCategoryFilters) { - TraceLog* trace_log = TraceLog::GetInstance(); - - trace_log->SetEnabled(TraceConfig(), TraceLog::RECORDING_MODE); - CheckTraceDefaultCategoryFilters(*trace_log); - trace_log->SetDisabled(); - - trace_log->SetEnabled(TraceConfig("", ""), TraceLog::RECORDING_MODE); - CheckTraceDefaultCategoryFilters(*trace_log); - trace_log->SetDisabled(); - - trace_log->SetEnabled(TraceConfig("*", ""), TraceLog::RECORDING_MODE); - CheckTraceDefaultCategoryFilters(*trace_log); - trace_log->SetDisabled(); - - trace_log->SetEnabled(TraceConfig(""), TraceLog::RECORDING_MODE); - CheckTraceDefaultCategoryFilters(*trace_log); - trace_log->SetDisabled(); -} - -TEST_F(TraceEventTestFixture, TraceWithDisabledByDefaultCategoryFilters) { - TraceLog* trace_log = TraceLog::GetInstance(); - - trace_log->SetEnabled(TraceConfig("foo,disabled-by-default-foo", ""), - TraceLog::RECORDING_MODE); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo")); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-foo")); - EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("bar")); - EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-bar")); - trace_log->SetDisabled(); - - // Enabling only the disabled-by-default-* category means the default ones - // are also enabled. - trace_log->SetEnabled(TraceConfig("disabled-by-default-foo", ""), - TraceLog::RECORDING_MODE); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-foo")); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo")); - EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("bar")); - EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-bar")); - trace_log->SetDisabled(); -} - -class MyData : public ConvertableToTraceFormat { - public: - MyData() {} - ~MyData() override {} - - void AppendAsTraceFormat(std::string* out) const override { - out->append("{\"foo\":1}"); - } - - private: - DISALLOW_COPY_AND_ASSIGN(MyData); -}; - -TEST_F(TraceEventTestFixture, ConvertableTypes) { - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - - std::unique_ptr data(new MyData()); - std::unique_ptr data1(new MyData()); - std::unique_ptr data2(new MyData()); - TRACE_EVENT1("foo", "bar", "data", std::move(data)); - TRACE_EVENT2("foo", "baz", "data1", std::move(data1), "data2", - std::move(data2)); - - // Check that std::unique_ptr are properly treated - // as - // convertable and not accidentally casted to bool. - std::unique_ptr convertData1(new MyData()); - std::unique_ptr convertData2(new MyData()); - std::unique_ptr convertData3(new MyData()); - std::unique_ptr convertData4(new MyData()); - TRACE_EVENT2("foo", "string_first", "str", "string value 1", "convert", - std::move(convertData1)); - TRACE_EVENT2("foo", "string_second", "convert", std::move(convertData2), - "str", "string value 2"); - TRACE_EVENT2("foo", "both_conv", "convert1", std::move(convertData3), - "convert2", std::move(convertData4)); - EndTraceAndFlush(); - - // One arg version. - DictionaryValue* dict = FindNamePhase("bar", "X"); - ASSERT_TRUE(dict); - - const DictionaryValue* args_dict = NULL; - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - - const Value* value = NULL; - const DictionaryValue* convertable_dict = NULL; - EXPECT_TRUE(args_dict->Get("data", &value)); - ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); - - int foo_val; - EXPECT_TRUE(convertable_dict->GetInteger("foo", &foo_val)); - EXPECT_EQ(1, foo_val); - - // Two arg version. - dict = FindNamePhase("baz", "X"); - ASSERT_TRUE(dict); - - args_dict = NULL; - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - - value = NULL; - convertable_dict = NULL; - EXPECT_TRUE(args_dict->Get("data1", &value)); - ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); - - value = NULL; - convertable_dict = NULL; - EXPECT_TRUE(args_dict->Get("data2", &value)); - ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); - - // Convertable with other types. - dict = FindNamePhase("string_first", "X"); - ASSERT_TRUE(dict); - - args_dict = NULL; - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - - std::string str_value; - EXPECT_TRUE(args_dict->GetString("str", &str_value)); - EXPECT_STREQ("string value 1", str_value.c_str()); - - value = NULL; - convertable_dict = NULL; - foo_val = 0; - EXPECT_TRUE(args_dict->Get("convert", &value)); - ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); - EXPECT_TRUE(convertable_dict->GetInteger("foo", &foo_val)); - EXPECT_EQ(1, foo_val); - - dict = FindNamePhase("string_second", "X"); - ASSERT_TRUE(dict); - - args_dict = NULL; - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - - EXPECT_TRUE(args_dict->GetString("str", &str_value)); - EXPECT_STREQ("string value 2", str_value.c_str()); - - value = NULL; - convertable_dict = NULL; - foo_val = 0; - EXPECT_TRUE(args_dict->Get("convert", &value)); - ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); - EXPECT_TRUE(convertable_dict->GetInteger("foo", &foo_val)); - EXPECT_EQ(1, foo_val); - - dict = FindNamePhase("both_conv", "X"); - ASSERT_TRUE(dict); - - args_dict = NULL; - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - - value = NULL; - convertable_dict = NULL; - foo_val = 0; - EXPECT_TRUE(args_dict->Get("convert1", &value)); - ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); - EXPECT_TRUE(args_dict->Get("convert2", &value)); - ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); -} - -TEST_F(TraceEventTestFixture, PrimitiveArgs) { - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - - TRACE_EVENT1("foo", "event1", "int_one", 1); - TRACE_EVENT1("foo", "event2", "int_neg_ten", -10); - TRACE_EVENT1("foo", "event3", "float_one", 1.0f); - TRACE_EVENT1("foo", "event4", "float_half", .5f); - TRACE_EVENT1("foo", "event5", "float_neghalf", -.5f); - TRACE_EVENT1("foo", "event6", "float_infinity", - std::numeric_limits::infinity()); - TRACE_EVENT1("foo", "event6b", "float_neg_infinity", - -std::numeric_limits::infinity()); - TRACE_EVENT1("foo", "event7", "double_nan", - std::numeric_limits::quiet_NaN()); - void* p = 0; - TRACE_EVENT1("foo", "event8", "pointer_null", p); - p = reinterpret_cast(0xbadf00d); - TRACE_EVENT1("foo", "event9", "pointer_badf00d", p); - TRACE_EVENT1("foo", "event10", "bool_true", true); - TRACE_EVENT1("foo", "event11", "bool_false", false); - TRACE_EVENT1("foo", "event12", "time_null", - base::Time()); - TRACE_EVENT1("foo", "event13", "time_one", - base::Time::FromInternalValue(1)); - TRACE_EVENT1("foo", "event14", "timeticks_null", - base::TimeTicks()); - TRACE_EVENT1("foo", "event15", "timeticks_one", - base::TimeTicks::FromInternalValue(1)); - EndTraceAndFlush(); - - const DictionaryValue* args_dict = NULL; - DictionaryValue* dict = NULL; - const Value* value = NULL; - std::string str_value; - int int_value; - double double_value; - bool bool_value; - - dict = FindNamePhase("event1", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetInteger("int_one", &int_value)); - EXPECT_EQ(1, int_value); - - dict = FindNamePhase("event2", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetInteger("int_neg_ten", &int_value)); - EXPECT_EQ(-10, int_value); - - // 1f must be serlized to JSON as "1.0" in order to be a double, not an int. - dict = FindNamePhase("event3", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->Get("float_one", &value)); - EXPECT_TRUE(value->IsType(Value::Type::DOUBLE)); - EXPECT_TRUE(value->GetAsDouble(&double_value)); - EXPECT_EQ(1, double_value); - - // .5f must be serlized to JSON as "0.5". - dict = FindNamePhase("event4", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->Get("float_half", &value)); - EXPECT_TRUE(value->IsType(Value::Type::DOUBLE)); - EXPECT_TRUE(value->GetAsDouble(&double_value)); - EXPECT_EQ(0.5, double_value); - - // -.5f must be serlized to JSON as "-0.5". - dict = FindNamePhase("event5", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->Get("float_neghalf", &value)); - EXPECT_TRUE(value->IsType(Value::Type::DOUBLE)); - EXPECT_TRUE(value->GetAsDouble(&double_value)); - EXPECT_EQ(-0.5, double_value); - - // Infinity is serialized to JSON as a string. - dict = FindNamePhase("event6", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetString("float_infinity", &str_value)); - EXPECT_STREQ("Infinity", str_value.c_str()); - dict = FindNamePhase("event6b", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetString("float_neg_infinity", &str_value)); - EXPECT_STREQ("-Infinity", str_value.c_str()); - - // NaN is serialized to JSON as a string. - dict = FindNamePhase("event7", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetString("double_nan", &str_value)); - EXPECT_STREQ("NaN", str_value.c_str()); - - // NULL pointers should be serialized as "0x0". - dict = FindNamePhase("event8", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetString("pointer_null", &str_value)); - EXPECT_STREQ("0x0", str_value.c_str()); - - // Other pointers should be serlized as a hex string. - dict = FindNamePhase("event9", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetString("pointer_badf00d", &str_value)); - EXPECT_STREQ("0xbadf00d", str_value.c_str()); - - dict = FindNamePhase("event10", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetBoolean("bool_true", &bool_value)); - EXPECT_TRUE(bool_value); - - dict = FindNamePhase("event11", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetBoolean("bool_false", &bool_value)); - EXPECT_FALSE(bool_value); - - dict = FindNamePhase("event12", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetInteger("time_null", &int_value)); - EXPECT_EQ(0, int_value); - - dict = FindNamePhase("event13", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetInteger("time_one", &int_value)); - EXPECT_EQ(1, int_value); - - dict = FindNamePhase("event14", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetInteger("timeticks_null", &int_value)); - EXPECT_EQ(0, int_value); - - dict = FindNamePhase("event15", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetInteger("timeticks_one", &int_value)); - EXPECT_EQ(1, int_value); -} - -TEST_F(TraceEventTestFixture, NameIsEscaped) { - TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), - TraceLog::RECORDING_MODE); - TRACE_EVENT0("category", "name\\with\\backspaces"); - EndTraceAndFlush(); - - EXPECT_TRUE(FindMatchingValue("cat", "category")); - EXPECT_TRUE(FindMatchingValue("name", "name\\with\\backspaces")); -} - -namespace { - -bool IsArgNameWhitelisted(const char* arg_name) { - return base::MatchPattern(arg_name, "granular_arg_whitelisted"); -} - -bool IsTraceEventArgsWhitelisted(const char* category_group_name, - const char* event_name, - ArgumentNameFilterPredicate* arg_filter) { - if (base::MatchPattern(category_group_name, "toplevel") && - base::MatchPattern(event_name, "*")) { - return true; - } - - if (base::MatchPattern(category_group_name, "benchmark") && - base::MatchPattern(event_name, "granularly_whitelisted")) { - *arg_filter = base::Bind(&IsArgNameWhitelisted); - return true; - } - - return false; -} - -} // namespace - -TEST_F(TraceEventTestFixture, ArgsWhitelisting) { - TraceLog::GetInstance()->SetArgumentFilterPredicate( - base::Bind(&IsTraceEventArgsWhitelisted)); - - TraceLog::GetInstance()->SetEnabled( - TraceConfig(kRecordAllCategoryFilter, "enable-argument-filter"), - TraceLog::RECORDING_MODE); - - TRACE_EVENT1("toplevel", "event1", "int_one", 1); - TRACE_EVENT1("whitewashed", "event2", "int_two", 1); - - TRACE_EVENT2("benchmark", "granularly_whitelisted", - "granular_arg_whitelisted", "whitelisted_value", - "granular_arg_blacklisted", "blacklisted_value"); - - EndTraceAndFlush(); - - const DictionaryValue* args_dict = NULL; - DictionaryValue* dict = NULL; - int int_value; - - dict = FindNamePhase("event1", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_TRUE(args_dict->GetInteger("int_one", &int_value)); - EXPECT_EQ(1, int_value); - - dict = FindNamePhase("event2", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - EXPECT_FALSE(args_dict->GetInteger("int_two", &int_value)); - - std::string args_string; - EXPECT_TRUE(dict->GetString("args", &args_string)); - EXPECT_EQ(args_string, "__stripped__"); - - dict = FindNamePhase("granularly_whitelisted", "X"); - ASSERT_TRUE(dict); - dict->GetDictionary("args", &args_dict); - ASSERT_TRUE(args_dict); - - EXPECT_TRUE(args_dict->GetString("granular_arg_whitelisted", &args_string)); - EXPECT_EQ(args_string, "whitelisted_value"); - - EXPECT_TRUE(args_dict->GetString("granular_arg_blacklisted", &args_string)); - EXPECT_EQ(args_string, "__stripped__"); -} - -TEST_F(TraceEventTestFixture, TraceBufferVectorReportFull) { - TraceLog* trace_log = TraceLog::GetInstance(); - trace_log->SetEnabled( - TraceConfig(kRecordAllCategoryFilter, ""), TraceLog::RECORDING_MODE); - trace_log->logged_events_.reset( - TraceBuffer::CreateTraceBufferVectorOfSize(100)); - do { - TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0( - "all", "with_timestamp", 0, 0, TimeTicks::Now()); - TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0( - "all", "with_timestamp", 0, 0, TimeTicks::Now()); - } while (!trace_log->BufferIsFull()); - - EndTraceAndFlush(); - - const DictionaryValue* trace_full_metadata = NULL; - - trace_full_metadata = FindTraceEntry(trace_parsed_, - "overflowed_at_ts"); - std::string phase; - double buffer_limit_reached_timestamp = 0; - - EXPECT_TRUE(trace_full_metadata); - EXPECT_TRUE(trace_full_metadata->GetString("ph", &phase)); - EXPECT_EQ("M", phase); - EXPECT_TRUE(trace_full_metadata->GetDouble( - "args.overflowed_at_ts", &buffer_limit_reached_timestamp)); - EXPECT_DOUBLE_EQ( - static_cast( - trace_log->buffer_limit_reached_timestamp_.ToInternalValue()), - buffer_limit_reached_timestamp); - - // Test that buffer_limit_reached_timestamp's value is between the timestamp - // of the last trace event and current time. - DropTracedMetadataRecords(); - const DictionaryValue* last_trace_event = NULL; - double last_trace_event_timestamp = 0; - EXPECT_TRUE(trace_parsed_.GetDictionary(trace_parsed_.GetSize() - 1, - &last_trace_event)); - EXPECT_TRUE(last_trace_event->GetDouble("ts", &last_trace_event_timestamp)); - EXPECT_LE(last_trace_event_timestamp, buffer_limit_reached_timestamp); - EXPECT_LE(buffer_limit_reached_timestamp, - trace_log->OffsetNow().ToInternalValue()); -} - -TEST_F(TraceEventTestFixture, TraceBufferRingBufferGetReturnChunk) { - TraceLog::GetInstance()->SetEnabled( - TraceConfig(kRecordAllCategoryFilter, RECORD_CONTINUOUSLY), - TraceLog::RECORDING_MODE); - TraceBuffer* buffer = TraceLog::GetInstance()->trace_buffer(); - size_t capacity = buffer->Capacity(); - size_t num_chunks = capacity / TraceBufferChunk::kTraceBufferChunkSize; - uint32_t last_seq = 0; - size_t chunk_index; - EXPECT_EQ(0u, buffer->Size()); - - std::unique_ptr chunks( - new TraceBufferChunk*[num_chunks]); - for (size_t i = 0; i < num_chunks; ++i) { - chunks[i] = buffer->GetChunk(&chunk_index).release(); - EXPECT_TRUE(chunks[i]); - EXPECT_EQ(i, chunk_index); - EXPECT_GT(chunks[i]->seq(), last_seq); - EXPECT_EQ((i + 1) * TraceBufferChunk::kTraceBufferChunkSize, - buffer->Size()); - last_seq = chunks[i]->seq(); - } - - // Ring buffer is never full. - EXPECT_FALSE(buffer->IsFull()); - - // Return all chunks in original order. - for (size_t i = 0; i < num_chunks; ++i) - buffer->ReturnChunk(i, std::unique_ptr(chunks[i])); - - // Should recycle the chunks in the returned order. - for (size_t i = 0; i < num_chunks; ++i) { - chunks[i] = buffer->GetChunk(&chunk_index).release(); - EXPECT_TRUE(chunks[i]); - EXPECT_EQ(i, chunk_index); - EXPECT_GT(chunks[i]->seq(), last_seq); - last_seq = chunks[i]->seq(); - } - - // Return all chunks in reverse order. - for (size_t i = 0; i < num_chunks; ++i) { - buffer->ReturnChunk(num_chunks - i - 1, std::unique_ptr( - chunks[num_chunks - i - 1])); - } - - // Should recycle the chunks in the returned order. - for (size_t i = 0; i < num_chunks; ++i) { - chunks[i] = buffer->GetChunk(&chunk_index).release(); - EXPECT_TRUE(chunks[i]); - EXPECT_EQ(num_chunks - i - 1, chunk_index); - EXPECT_GT(chunks[i]->seq(), last_seq); - last_seq = chunks[i]->seq(); - } - - for (size_t i = 0; i < num_chunks; ++i) - buffer->ReturnChunk(i, std::unique_ptr(chunks[i])); - - TraceLog::GetInstance()->SetDisabled(); -} - -TEST_F(TraceEventTestFixture, TraceBufferRingBufferHalfIteration) { - TraceLog::GetInstance()->SetEnabled( - TraceConfig(kRecordAllCategoryFilter, RECORD_CONTINUOUSLY), - TraceLog::RECORDING_MODE); - TraceBuffer* buffer = TraceLog::GetInstance()->trace_buffer(); - size_t capacity = buffer->Capacity(); - size_t num_chunks = capacity / TraceBufferChunk::kTraceBufferChunkSize; - size_t chunk_index; - EXPECT_EQ(0u, buffer->Size()); - EXPECT_FALSE(buffer->NextChunk()); - - size_t half_chunks = num_chunks / 2; - std::unique_ptr chunks( - new TraceBufferChunk*[half_chunks]); - - for (size_t i = 0; i < half_chunks; ++i) { - chunks[i] = buffer->GetChunk(&chunk_index).release(); - EXPECT_TRUE(chunks[i]); - EXPECT_EQ(i, chunk_index); - } - for (size_t i = 0; i < half_chunks; ++i) - buffer->ReturnChunk(i, std::unique_ptr(chunks[i])); - - for (size_t i = 0; i < half_chunks; ++i) - EXPECT_EQ(chunks[i], buffer->NextChunk()); - EXPECT_FALSE(buffer->NextChunk()); - TraceLog::GetInstance()->SetDisabled(); -} - -TEST_F(TraceEventTestFixture, TraceBufferRingBufferFullIteration) { - TraceLog::GetInstance()->SetEnabled( - TraceConfig(kRecordAllCategoryFilter, RECORD_CONTINUOUSLY), - TraceLog::RECORDING_MODE); - TraceBuffer* buffer = TraceLog::GetInstance()->trace_buffer(); - size_t capacity = buffer->Capacity(); - size_t num_chunks = capacity / TraceBufferChunk::kTraceBufferChunkSize; - size_t chunk_index; - EXPECT_EQ(0u, buffer->Size()); - EXPECT_FALSE(buffer->NextChunk()); - - std::unique_ptr chunks( - new TraceBufferChunk*[num_chunks]); - - for (size_t i = 0; i < num_chunks; ++i) { - chunks[i] = buffer->GetChunk(&chunk_index).release(); - EXPECT_TRUE(chunks[i]); - EXPECT_EQ(i, chunk_index); - } - for (size_t i = 0; i < num_chunks; ++i) - buffer->ReturnChunk(i, std::unique_ptr(chunks[i])); - - for (size_t i = 0; i < num_chunks; ++i) - EXPECT_TRUE(chunks[i] == buffer->NextChunk()); - EXPECT_FALSE(buffer->NextChunk()); - TraceLog::GetInstance()->SetDisabled(); -} - -TEST_F(TraceEventTestFixture, TraceRecordAsMuchAsPossibleMode) { - TraceLog::GetInstance()->SetEnabled( - TraceConfig(kRecordAllCategoryFilter, RECORD_AS_MUCH_AS_POSSIBLE), - TraceLog::RECORDING_MODE); - TraceBuffer* buffer = TraceLog::GetInstance()->trace_buffer(); - EXPECT_EQ(512000000UL, buffer->Capacity()); - TraceLog::GetInstance()->SetDisabled(); -} - -void BlockUntilStopped(WaitableEvent* task_start_event, - WaitableEvent* task_stop_event) { - task_start_event->Signal(); - task_stop_event->Wait(); -} - -TEST_F(TraceEventTestFixture, SetCurrentThreadBlocksMessageLoopBeforeTracing) { - BeginTrace(); - - Thread thread("1"); - WaitableEvent task_complete_event(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - thread.Start(); - thread.task_runner()->PostTask( - FROM_HERE, Bind(&TraceLog::SetCurrentThreadBlocksMessageLoop, - Unretained(TraceLog::GetInstance()))); - - thread.task_runner()->PostTask( - FROM_HERE, Bind(&TraceWithAllMacroVariants, &task_complete_event)); - task_complete_event.Wait(); - - WaitableEvent task_start_event(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - WaitableEvent task_stop_event(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - thread.task_runner()->PostTask( - FROM_HERE, Bind(&BlockUntilStopped, &task_start_event, &task_stop_event)); - task_start_event.Wait(); - - EndTraceAndFlush(); - ValidateAllTraceMacrosCreatedData(trace_parsed_); - - task_stop_event.Signal(); - thread.Stop(); -} - -TEST_F(TraceEventTestFixture, ConvertTraceConfigToInternalOptions) { - TraceLog* trace_log = TraceLog::GetInstance(); - EXPECT_EQ(TraceLog::kInternalRecordUntilFull, - trace_log->GetInternalOptionsFromTraceConfig( - TraceConfig(kRecordAllCategoryFilter, RECORD_UNTIL_FULL))); - - EXPECT_EQ(TraceLog::kInternalRecordContinuously, - trace_log->GetInternalOptionsFromTraceConfig( - TraceConfig(kRecordAllCategoryFilter, RECORD_CONTINUOUSLY))); - - EXPECT_EQ(TraceLog::kInternalEchoToConsole, - trace_log->GetInternalOptionsFromTraceConfig( - TraceConfig(kRecordAllCategoryFilter, ECHO_TO_CONSOLE))); - - EXPECT_EQ(TraceLog::kInternalEchoToConsole, - trace_log->GetInternalOptionsFromTraceConfig( - TraceConfig("*", "trace-to-console,enable-systrace"))); -} - -void SetBlockingFlagAndBlockUntilStopped(WaitableEvent* task_start_event, - WaitableEvent* task_stop_event) { - TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop(); - BlockUntilStopped(task_start_event, task_stop_event); -} - -TEST_F(TraceEventTestFixture, SetCurrentThreadBlocksMessageLoopAfterTracing) { - BeginTrace(); - - Thread thread("1"); - WaitableEvent task_complete_event(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - thread.Start(); - - thread.task_runner()->PostTask( - FROM_HERE, Bind(&TraceWithAllMacroVariants, &task_complete_event)); - task_complete_event.Wait(); - - WaitableEvent task_start_event(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - WaitableEvent task_stop_event(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - thread.task_runner()->PostTask( - FROM_HERE, Bind(&SetBlockingFlagAndBlockUntilStopped, &task_start_event, - &task_stop_event)); - task_start_event.Wait(); - - EndTraceAndFlush(); - ValidateAllTraceMacrosCreatedData(trace_parsed_); - - task_stop_event.Signal(); - thread.Stop(); -} - -TEST_F(TraceEventTestFixture, ThreadOnceBlocking) { - BeginTrace(); - - Thread thread("1"); - WaitableEvent task_complete_event(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - thread.Start(); - - thread.task_runner()->PostTask( - FROM_HERE, Bind(&TraceWithAllMacroVariants, &task_complete_event)); - task_complete_event.Wait(); - task_complete_event.Reset(); - - WaitableEvent task_start_event(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - WaitableEvent task_stop_event(WaitableEvent::ResetPolicy::AUTOMATIC, - WaitableEvent::InitialState::NOT_SIGNALED); - thread.task_runner()->PostTask( - FROM_HERE, Bind(&BlockUntilStopped, &task_start_event, &task_stop_event)); - task_start_event.Wait(); - - // The thread will timeout in this flush. - EndTraceAndFlushInThreadWithMessageLoop(); - Clear(); - - // Let the thread's message loop continue to spin. - task_stop_event.Signal(); - - // The following sequence ensures that the FlushCurrentThread task has been - // executed in the thread before continuing. - task_start_event.Reset(); - task_stop_event.Reset(); - thread.task_runner()->PostTask( - FROM_HERE, Bind(&BlockUntilStopped, &task_start_event, &task_stop_event)); - task_start_event.Wait(); - task_stop_event.Signal(); - Clear(); - - // TraceLog should discover the generation mismatch and recover the thread - // local buffer for the thread without any error. - BeginTrace(); - thread.task_runner()->PostTask( - FROM_HERE, Bind(&TraceWithAllMacroVariants, &task_complete_event)); - task_complete_event.Wait(); - task_complete_event.Reset(); - EndTraceAndFlushInThreadWithMessageLoop(); - ValidateAllTraceMacrosCreatedData(trace_parsed_); -} - -std::string* g_log_buffer = NULL; -bool MockLogMessageHandler(int, const char*, int, size_t, - const std::string& str) { - if (!g_log_buffer) - g_log_buffer = new std::string(); - g_log_buffer->append(str); - return false; -} - -TEST_F(TraceEventTestFixture, EchoToConsole) { - logging::LogMessageHandlerFunction old_log_message_handler = - logging::GetLogMessageHandler(); - logging::SetLogMessageHandler(MockLogMessageHandler); - - TraceLog::GetInstance()->SetEnabled( - TraceConfig(kRecordAllCategoryFilter, ECHO_TO_CONSOLE), - TraceLog::RECORDING_MODE); - TRACE_EVENT_BEGIN0("a", "begin_end"); - { - TRACE_EVENT0("b", "duration"); - TRACE_EVENT0("b1", "duration1"); - } - TRACE_EVENT_INSTANT0("c", "instant", TRACE_EVENT_SCOPE_GLOBAL); - TRACE_EVENT_END0("a", "begin_end"); - - EXPECT_NE(std::string::npos, g_log_buffer->find("begin_end[a]\x1b")); - EXPECT_NE(std::string::npos, g_log_buffer->find("| duration[b]\x1b")); - EXPECT_NE(std::string::npos, g_log_buffer->find("| | duration1[b1]\x1b")); - EXPECT_NE(std::string::npos, g_log_buffer->find("| | duration1[b1] (")); - EXPECT_NE(std::string::npos, g_log_buffer->find("| duration[b] (")); - EXPECT_NE(std::string::npos, g_log_buffer->find("| instant[c]\x1b")); - EXPECT_NE(std::string::npos, g_log_buffer->find("begin_end[a] (")); - - EndTraceAndFlush(); - delete g_log_buffer; - logging::SetLogMessageHandler(old_log_message_handler); - g_log_buffer = NULL; -} - -bool LogMessageHandlerWithTraceEvent(int, const char*, int, size_t, - const std::string&) { - TRACE_EVENT0("log", "trace_event"); - return false; -} - -TEST_F(TraceEventTestFixture, EchoToConsoleTraceEventRecursion) { - logging::LogMessageHandlerFunction old_log_message_handler = - logging::GetLogMessageHandler(); - logging::SetLogMessageHandler(LogMessageHandlerWithTraceEvent); - - TraceLog::GetInstance()->SetEnabled( - TraceConfig(kRecordAllCategoryFilter, ECHO_TO_CONSOLE), - TraceLog::RECORDING_MODE); - { - // This should not cause deadlock or infinite recursion. - TRACE_EVENT0("b", "duration"); - } - - EndTraceAndFlush(); - logging::SetLogMessageHandler(old_log_message_handler); -} - -TEST_F(TraceEventTestFixture, TimeOffset) { - BeginTrace(); - // Let TraceLog timer start from 0. - TimeDelta time_offset = TimeTicks::Now() - TimeTicks(); - TraceLog::GetInstance()->SetTimeOffset(time_offset); - - { - TRACE_EVENT0("all", "duration1"); - TRACE_EVENT0("all", "duration2"); - } - TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0( - "all", "with_timestamp", 0, 0, TimeTicks::Now()); - TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0( - "all", "with_timestamp", 0, 0, TimeTicks::Now()); - - EndTraceAndFlush(); - DropTracedMetadataRecords(); - - double end_time = static_cast( - (TimeTicks::Now() - time_offset).ToInternalValue()); - double last_timestamp = 0; - for (size_t i = 0; i < trace_parsed_.GetSize(); ++i) { - const DictionaryValue* item; - EXPECT_TRUE(trace_parsed_.GetDictionary(i, &item)); - double timestamp; - EXPECT_TRUE(item->GetDouble("ts", ×tamp)); - EXPECT_GE(timestamp, last_timestamp); - EXPECT_LE(timestamp, end_time); - last_timestamp = timestamp; - } -} - -TEST_F(TraceEventTestFixture, ConfigureSyntheticDelays) { - BeginSpecificTrace("DELAY(test.Delay;0.05)"); - - base::TimeTicks start = base::TimeTicks::Now(); - { - TRACE_EVENT_SYNTHETIC_DELAY("test.Delay"); - } - base::TimeDelta duration = base::TimeTicks::Now() - start; - EXPECT_GE(duration.InMilliseconds(), 50); - - EndTraceAndFlush(); -} - -TEST_F(TraceEventTestFixture, BadSyntheticDelayConfigurations) { - const char* const filters[] = { - "", - "DELAY(", - "DELAY(;", - "DELAY(;)", - "DELAY(test.Delay)", - "DELAY(test.Delay;)" - }; - for (size_t i = 0; i < arraysize(filters); i++) { - BeginSpecificTrace(filters[i]); - EndTraceAndFlush(); - TraceConfig trace_config = TraceLog::GetInstance()->GetCurrentTraceConfig(); - EXPECT_EQ(0u, trace_config.GetSyntheticDelayValues().size()); - } -} - -TEST_F(TraceEventTestFixture, SyntheticDelayConfigurationMerging) { - TraceConfig config1("DELAY(test.Delay1;16)", ""); - TraceConfig config2("DELAY(test.Delay2;32)", ""); - config1.Merge(config2); - EXPECT_EQ(2u, config1.GetSyntheticDelayValues().size()); -} - -TEST_F(TraceEventTestFixture, SyntheticDelayConfigurationToString) { - const char filter[] = "DELAY(test.Delay;16;oneshot)"; - TraceConfig config(filter, ""); - EXPECT_EQ(filter, config.ToCategoryFilterString()); -} - -TEST_F(TraceEventTestFixture, TraceFilteringMode) { - const char config_json[] = - "{" - " \"event_filters\": [" - " {" - " \"filter_predicate\": \"testing_predicate\", " - " \"included_categories\": [\"*\"]" - " }" - " ]" - "}"; - - // Run RECORDING_MODE within FILTERING_MODE: - TestEventFilter::HitsCounter filter_hits_counter; - TestEventFilter::set_filter_return_value(true); - TraceLog::GetInstance()->SetFilterFactoryForTesting(TestEventFilter::Factory); - - // Only filtering mode is enabled with test filters. - TraceLog::GetInstance()->SetEnabled(TraceConfig(config_json), - TraceLog::FILTERING_MODE); - EXPECT_EQ(TraceLog::FILTERING_MODE, TraceLog::GetInstance()->enabled_modes()); - { - void* ptr = this; - TRACE_EVENT0("c0", "name0"); - TRACE_EVENT_ASYNC_BEGIN0("c1", "name1", ptr); - TRACE_EVENT_INSTANT0("c0", "name0", TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_ASYNC_END0("c1", "name1", ptr); - } - - // Recording mode is enabled when filtering mode is turned on. - TraceLog::GetInstance()->SetEnabled(TraceConfig("", ""), - TraceLog::RECORDING_MODE); - EXPECT_EQ(TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE, - TraceLog::GetInstance()->enabled_modes()); - { - TRACE_EVENT0("c2", "name2"); - } - // Only recording mode is disabled and filtering mode will continue to run. - TraceLog::GetInstance()->SetDisabled(TraceLog::RECORDING_MODE); - EXPECT_EQ(TraceLog::FILTERING_MODE, TraceLog::GetInstance()->enabled_modes()); - - { - TRACE_EVENT0("c0", "name0"); - } - // Filtering mode is disabled and no tracing mode should be enabled. - TraceLog::GetInstance()->SetDisabled(TraceLog::FILTERING_MODE); - EXPECT_EQ(0, TraceLog::GetInstance()->enabled_modes()); - - EndTraceAndFlush(); - EXPECT_FALSE(FindMatchingValue("cat", "c0")); - EXPECT_FALSE(FindMatchingValue("cat", "c1")); - EXPECT_FALSE(FindMatchingValue("name", "name0")); - EXPECT_FALSE(FindMatchingValue("name", "name1")); - EXPECT_TRUE(FindMatchingValue("cat", "c2")); - EXPECT_TRUE(FindMatchingValue("name", "name2")); - EXPECT_EQ(6u, filter_hits_counter.filter_trace_event_hit_count); - EXPECT_EQ(3u, filter_hits_counter.end_event_hit_count); - Clear(); - filter_hits_counter.Reset(); - - // Run FILTERING_MODE within RECORDING_MODE: - // Only recording mode is enabled and all events must be recorded. - TraceLog::GetInstance()->SetEnabled(TraceConfig("", ""), - TraceLog::RECORDING_MODE); - EXPECT_EQ(TraceLog::RECORDING_MODE, TraceLog::GetInstance()->enabled_modes()); - { - TRACE_EVENT0("c0", "name0"); - } - - // Filtering mode is also enabled and all events must be filtered-out. - TestEventFilter::set_filter_return_value(false); - TraceLog::GetInstance()->SetEnabled(TraceConfig(config_json), - TraceLog::FILTERING_MODE); - EXPECT_EQ(TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE, - TraceLog::GetInstance()->enabled_modes()); - { - TRACE_EVENT0("c1", "name1"); - } - // Only filtering mode is disabled and recording mode should continue to run - // with all events being recorded. - TraceLog::GetInstance()->SetDisabled(TraceLog::FILTERING_MODE); - EXPECT_EQ(TraceLog::RECORDING_MODE, TraceLog::GetInstance()->enabled_modes()); - - { - TRACE_EVENT0("c2", "name2"); - } - // Recording mode is disabled and no tracing mode should be enabled. - TraceLog::GetInstance()->SetDisabled(TraceLog::RECORDING_MODE); - EXPECT_EQ(0, TraceLog::GetInstance()->enabled_modes()); - - EndTraceAndFlush(); - EXPECT_TRUE(FindMatchingValue("cat", "c0")); - EXPECT_TRUE(FindMatchingValue("cat", "c2")); - EXPECT_TRUE(FindMatchingValue("name", "name0")); - EXPECT_TRUE(FindMatchingValue("name", "name2")); - EXPECT_FALSE(FindMatchingValue("cat", "c1")); - EXPECT_FALSE(FindMatchingValue("name", "name1")); - EXPECT_EQ(1u, filter_hits_counter.filter_trace_event_hit_count); - EXPECT_EQ(1u, filter_hits_counter.end_event_hit_count); - Clear(); -} - -TEST_F(TraceEventTestFixture, EventFiltering) { - const char config_json[] = - "{" - " \"included_categories\": [" - " \"filtered_cat\"," - " \"unfiltered_cat\"," - " \"" TRACE_DISABLED_BY_DEFAULT("filtered_cat") "\"," - " \"" TRACE_DISABLED_BY_DEFAULT("unfiltered_cat") "\"]," - " \"event_filters\": [" - " {" - " \"filter_predicate\": \"testing_predicate\", " - " \"included_categories\": [" - " \"filtered_cat\"," - " \"" TRACE_DISABLED_BY_DEFAULT("filtered_cat") "\"]" - " }" - " " - " ]" - "}"; - - TestEventFilter::HitsCounter filter_hits_counter; - TestEventFilter::set_filter_return_value(true); - TraceLog::GetInstance()->SetFilterFactoryForTesting(TestEventFilter::Factory); - - TraceConfig trace_config(config_json); - TraceLog::GetInstance()->SetEnabled( - trace_config, TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE); - ASSERT_TRUE(TraceLog::GetInstance()->IsEnabled()); - - TRACE_EVENT0("filtered_cat", "a snake"); - TRACE_EVENT0("filtered_cat", "a mushroom"); - TRACE_EVENT0("unfiltered_cat", "a horse"); - - TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("filtered_cat"), "a dog"); - TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("unfiltered_cat"), "a pony"); - - // This is scoped so we can test the end event being filtered. - { TRACE_EVENT0("filtered_cat", "another cat whoa"); } - - EndTraceAndFlush(); - - EXPECT_EQ(4u, filter_hits_counter.filter_trace_event_hit_count); - EXPECT_EQ(1u, filter_hits_counter.end_event_hit_count); -} - -TEST_F(TraceEventTestFixture, EventWhitelistFiltering) { - std::string config_json = StringPrintf( - "{" - " \"included_categories\": [" - " \"filtered_cat\"," - " \"unfiltered_cat\"," - " \"" TRACE_DISABLED_BY_DEFAULT("filtered_cat") "\"]," - " \"event_filters\": [" - " {" - " \"filter_predicate\": \"%s\", " - " \"included_categories\": [" - " \"filtered_cat\"," - " \"" TRACE_DISABLED_BY_DEFAULT("*") "\"], " - " \"filter_args\": {" - " \"event_name_whitelist\": [\"a snake\", \"a dog\"]" - " }" - " }" - " " - " ]" - "}", - EventNameFilter::kName); - - TraceConfig trace_config(config_json); - TraceLog::GetInstance()->SetEnabled( - trace_config, TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE); - EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled()); - - TRACE_EVENT0("filtered_cat", "a snake"); - TRACE_EVENT0("filtered_cat", "a mushroom"); - TRACE_EVENT0("unfiltered_cat", "a cat"); - TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("filtered_cat"), "a dog"); - TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("filtered_cat"), "a pony"); - - EndTraceAndFlush(); - - EXPECT_TRUE(FindMatchingValue("name", "a snake")); - EXPECT_FALSE(FindMatchingValue("name", "a mushroom")); - EXPECT_TRUE(FindMatchingValue("name", "a cat")); - EXPECT_TRUE(FindMatchingValue("name", "a dog")); - EXPECT_FALSE(FindMatchingValue("name", "a pony")); -} - -TEST_F(TraceEventTestFixture, HeapProfilerFiltering) { - std::string config_json = StringPrintf( - "{" - " \"included_categories\": [" - " \"filtered_cat\"," - " \"unfiltered_cat\"," - " \"" TRACE_DISABLED_BY_DEFAULT("filtered_cat") "\"," - " \"" TRACE_DISABLED_BY_DEFAULT("unfiltered_cat") "\"]," - " \"excluded_categories\": [\"excluded_cat\"]," - " \"event_filters\": [" - " {" - " \"filter_predicate\": \"%s\", " - " \"included_categories\": [" - " \"*\"," - " \"" TRACE_DISABLED_BY_DEFAULT("filtered_cat") "\"]" - " }" - " ]" - "}", - HeapProfilerEventFilter::kName); - - TraceConfig trace_config(config_json); - TraceLog::GetInstance()->SetEnabled( - trace_config, TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE); - EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled()); - - TRACE_EVENT0("filtered_cat", "a snake"); - TRACE_EVENT0("excluded_cat", "a mushroom"); - TRACE_EVENT0("unfiltered_cat", "a cat"); - TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("filtered_cat"), "a dog"); - TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("unfiltered_cat"), "a pony"); - - EndTraceAndFlush(); - - // The predicate should not change behavior of the trace events. - EXPECT_TRUE(FindMatchingValue("name", "a snake")); - EXPECT_FALSE(FindMatchingValue("name", "a mushroom")); - EXPECT_TRUE(FindMatchingValue("name", "a cat")); - EXPECT_TRUE(FindMatchingValue("name", "a dog")); - EXPECT_TRUE(FindMatchingValue("name", "a pony")); -} - -TEST_F(TraceEventTestFixture, ClockSyncEventsAreAlwaysAddedToTrace) { - BeginSpecificTrace("-*"); - TRACE_EVENT_CLOCK_SYNC_RECEIVER(1); - EndTraceAndFlush(); - EXPECT_TRUE(FindNamePhase("clock_sync", "c")); -} - -} // namespace trace_event -} // namespace base diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc deleted file mode 100644 index d08030e709ddaef4c36cd655c986f0622f02e3b6..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_log.cc +++ /dev/null @@ -1,1749 +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. - -#include "base/trace_event/trace_log.h" - -#include -#include -#include -#include - -#include "base/base_switches.h" -#include "base/bind.h" -#include "base/command_line.h" -#include "base/debug/leak_annotations.h" -#include "base/location.h" -#include "base/macros.h" -#include "base/memory/ptr_util.h" -#include "base/memory/ref_counted_memory.h" -#include "base/memory/singleton.h" -#include "base/message_loop/message_loop.h" -#include "base/process/process_info.h" -#include "base/process/process_metrics.h" -#include "base/stl_util.h" -#include "base/strings/string_piece.h" -#include "base/strings/string_split.h" -#include "base/strings/string_tokenizer.h" -#include "base/strings/stringprintf.h" -#include "base/sys_info.h" -// post_task.h pulls in a lot of code not needed on Arc++. -#if 0 -#include "base/task_scheduler/post_task.h" -#endif -#include "base/threading/platform_thread.h" -#include "base/threading/thread_id_name_manager.h" -#include "base/threading/thread_task_runner_handle.h" -#include "base/time/time.h" -#include "base/trace_event/category_registry.h" -#include "base/trace_event/event_name_filter.h" -#include "base/trace_event/heap_profiler.h" -#include "base/trace_event/heap_profiler_allocation_context_tracker.h" -#include "base/trace_event/heap_profiler_event_filter.h" -#include "base/trace_event/memory_dump_manager.h" -#include "base/trace_event/memory_dump_provider.h" -#include "base/trace_event/process_memory_dump.h" -#include "base/trace_event/trace_buffer.h" -#include "base/trace_event/trace_event.h" -#include "base/trace_event/trace_event_synthetic_delay.h" -#include "build/build_config.h" - -#if defined(OS_WIN) -#include "base/trace_event/trace_event_etw_export_win.h" -#endif - -namespace base { -namespace internal { - -class DeleteTraceLogForTesting { - public: - static void Delete() { - Singleton>::OnExit(0); - } -}; - -} // namespace internal - -namespace trace_event { - -namespace { - -// Controls the number of trace events we will buffer in-memory -// before throwing them away. -const size_t kTraceBufferChunkSize = TraceBufferChunk::kTraceBufferChunkSize; - -const size_t kTraceEventVectorBigBufferChunks = - 512000000 / kTraceBufferChunkSize; -static_assert( - kTraceEventVectorBigBufferChunks <= TraceBufferChunk::kMaxChunkIndex, - "Too many big buffer chunks"); -const size_t kTraceEventVectorBufferChunks = 256000 / kTraceBufferChunkSize; -static_assert( - kTraceEventVectorBufferChunks <= TraceBufferChunk::kMaxChunkIndex, - "Too many vector buffer chunks"); -const size_t kTraceEventRingBufferChunks = kTraceEventVectorBufferChunks / 4; - -// ECHO_TO_CONSOLE needs a small buffer to hold the unfinished COMPLETE events. -const size_t kEchoToConsoleTraceEventBufferChunks = 256; - -const size_t kTraceEventBufferSizeInBytes = 100 * 1024; -const int kThreadFlushTimeoutMs = 3000; - -#define MAX_TRACE_EVENT_FILTERS 32 - -// List of TraceEventFilter objects from the most recent tracing session. -std::vector>& GetCategoryGroupFilters() { - static auto* filters = new std::vector>(); - return *filters; -} - -ThreadTicks ThreadNow() { - return ThreadTicks::IsSupported() ? ThreadTicks::Now() : ThreadTicks(); -} - -template -void InitializeMetadataEvent(TraceEvent* trace_event, - int thread_id, - const char* metadata_name, - const char* arg_name, - const T& value) { - if (!trace_event) - return; - - int num_args = 1; - unsigned char arg_type; - unsigned long long arg_value; - ::trace_event_internal::SetTraceValue(value, &arg_type, &arg_value); - trace_event->Initialize( - thread_id, - TimeTicks(), - ThreadTicks(), - TRACE_EVENT_PHASE_METADATA, - CategoryRegistry::kCategoryMetadata->state_ptr(), - metadata_name, - trace_event_internal::kGlobalScope, // scope - trace_event_internal::kNoId, // id - trace_event_internal::kNoId, // bind_id - num_args, - &arg_name, - &arg_type, - &arg_value, - nullptr, - TRACE_EVENT_FLAG_NONE); -} - -class AutoThreadLocalBoolean { - public: - explicit AutoThreadLocalBoolean(ThreadLocalBoolean* thread_local_boolean) - : thread_local_boolean_(thread_local_boolean) { - DCHECK(!thread_local_boolean_->Get()); - thread_local_boolean_->Set(true); - } - ~AutoThreadLocalBoolean() { thread_local_boolean_->Set(false); } - - private: - ThreadLocalBoolean* thread_local_boolean_; - DISALLOW_COPY_AND_ASSIGN(AutoThreadLocalBoolean); -}; - -// Use this function instead of TraceEventHandle constructor to keep the -// overhead of ScopedTracer (trace_event.h) constructor minimum. -void MakeHandle(uint32_t chunk_seq, - size_t chunk_index, - size_t event_index, - TraceEventHandle* handle) { - DCHECK(chunk_seq); - DCHECK(chunk_index <= TraceBufferChunk::kMaxChunkIndex); - DCHECK(event_index < TraceBufferChunk::kTraceBufferChunkSize); - DCHECK(chunk_index <= std::numeric_limits::max()); - handle->chunk_seq = chunk_seq; - handle->chunk_index = static_cast(chunk_index); - handle->event_index = static_cast(event_index); -} - -template -void ForEachCategoryFilter(const unsigned char* category_group_enabled, - Function filter_fn) { - const TraceCategory* category = - CategoryRegistry::GetCategoryByStatePtr(category_group_enabled); - uint32_t filter_bitmap = category->enabled_filters(); - for (int index = 0; filter_bitmap != 0; filter_bitmap >>= 1, index++) { - if (filter_bitmap & 1 && GetCategoryGroupFilters()[index]) - filter_fn(GetCategoryGroupFilters()[index].get()); - } -} - -} // namespace - -// A helper class that allows the lock to be acquired in the middle of the scope -// and unlocks at the end of scope if locked. -class TraceLog::OptionalAutoLock { - public: - explicit OptionalAutoLock(Lock* lock) : lock_(lock), locked_(false) {} - - ~OptionalAutoLock() { - if (locked_) - lock_->Release(); - } - - void EnsureAcquired() { - if (!locked_) { - lock_->Acquire(); - locked_ = true; - } - } - - private: - Lock* lock_; - bool locked_; - DISALLOW_COPY_AND_ASSIGN(OptionalAutoLock); -}; - -class TraceLog::ThreadLocalEventBuffer - : public MessageLoop::DestructionObserver, - public MemoryDumpProvider { - public: - explicit ThreadLocalEventBuffer(TraceLog* trace_log); - ~ThreadLocalEventBuffer() override; - - TraceEvent* AddTraceEvent(TraceEventHandle* handle); - - TraceEvent* GetEventByHandle(TraceEventHandle handle) { - if (!chunk_ || handle.chunk_seq != chunk_->seq() || - handle.chunk_index != chunk_index_) { - return nullptr; - } - - return chunk_->GetEventAt(handle.event_index); - } - - int generation() const { return generation_; } - - private: - // MessageLoop::DestructionObserver - void WillDestroyCurrentMessageLoop() override; - - // MemoryDumpProvider implementation. - bool OnMemoryDump(const MemoryDumpArgs& args, - ProcessMemoryDump* pmd) override; - - void FlushWhileLocked(); - - void CheckThisIsCurrentBuffer() const { - DCHECK(trace_log_->thread_local_event_buffer_.Get() == this); - } - - // Since TraceLog is a leaky singleton, trace_log_ will always be valid - // as long as the thread exists. - TraceLog* trace_log_; - std::unique_ptr chunk_; - size_t chunk_index_; - int generation_; - - DISALLOW_COPY_AND_ASSIGN(ThreadLocalEventBuffer); -}; - -TraceLog::ThreadLocalEventBuffer::ThreadLocalEventBuffer(TraceLog* trace_log) - : trace_log_(trace_log), - chunk_index_(0), - generation_(trace_log->generation()) { - // ThreadLocalEventBuffer is created only if the thread has a message loop, so - // the following message_loop won't be NULL. - MessageLoop* message_loop = MessageLoop::current(); - message_loop->AddDestructionObserver(this); - - // This is to report the local memory usage when memory-infra is enabled. - MemoryDumpManager::GetInstance()->RegisterDumpProvider( - this, "ThreadLocalEventBuffer", ThreadTaskRunnerHandle::Get()); - - AutoLock lock(trace_log->lock_); - trace_log->thread_message_loops_.insert(message_loop); -} - -TraceLog::ThreadLocalEventBuffer::~ThreadLocalEventBuffer() { - CheckThisIsCurrentBuffer(); - MessageLoop::current()->RemoveDestructionObserver(this); - MemoryDumpManager::GetInstance()->UnregisterDumpProvider(this); - - { - AutoLock lock(trace_log_->lock_); - FlushWhileLocked(); - trace_log_->thread_message_loops_.erase(MessageLoop::current()); - } - trace_log_->thread_local_event_buffer_.Set(NULL); -} - -TraceEvent* TraceLog::ThreadLocalEventBuffer::AddTraceEvent( - TraceEventHandle* handle) { - CheckThisIsCurrentBuffer(); - - if (chunk_ && chunk_->IsFull()) { - AutoLock lock(trace_log_->lock_); - FlushWhileLocked(); - chunk_.reset(); - } - if (!chunk_) { - AutoLock lock(trace_log_->lock_); - chunk_ = trace_log_->logged_events_->GetChunk(&chunk_index_); - trace_log_->CheckIfBufferIsFullWhileLocked(); - } - if (!chunk_) - return NULL; - - size_t event_index; - TraceEvent* trace_event = chunk_->AddTraceEvent(&event_index); - if (trace_event && handle) - MakeHandle(chunk_->seq(), chunk_index_, event_index, handle); - - return trace_event; -} - -void TraceLog::ThreadLocalEventBuffer::WillDestroyCurrentMessageLoop() { - delete this; -} - -bool TraceLog::ThreadLocalEventBuffer::OnMemoryDump(const MemoryDumpArgs& args, - ProcessMemoryDump* pmd) { - if (!chunk_) - return true; - std::string dump_base_name = StringPrintf( - "tracing/thread_%d", static_cast(PlatformThread::CurrentId())); - TraceEventMemoryOverhead overhead; - chunk_->EstimateTraceMemoryOverhead(&overhead); - overhead.DumpInto(dump_base_name.c_str(), pmd); - return true; -} - -void TraceLog::ThreadLocalEventBuffer::FlushWhileLocked() { - if (!chunk_) - return; - - trace_log_->lock_.AssertAcquired(); - if (trace_log_->CheckGeneration(generation_)) { - // Return the chunk to the buffer only if the generation matches. - trace_log_->logged_events_->ReturnChunk(chunk_index_, std::move(chunk_)); - } - // Otherwise this method may be called from the destructor, or TraceLog will - // find the generation mismatch and delete this buffer soon. -} - -struct TraceLog::RegisteredAsyncObserver { - explicit RegisteredAsyncObserver(WeakPtr observer) - : observer(observer), task_runner(ThreadTaskRunnerHandle::Get()) {} - ~RegisteredAsyncObserver() {} - - WeakPtr observer; - scoped_refptr task_runner; -}; - -TraceLogStatus::TraceLogStatus() : event_capacity(0), event_count(0) {} - -TraceLogStatus::~TraceLogStatus() {} - -// static -TraceLog* TraceLog::GetInstance() { - return Singleton>::get(); -} - -TraceLog::TraceLog() - : enabled_modes_(0), - num_traces_recorded_(0), - dispatching_to_observer_list_(false), - process_sort_index_(0), - process_id_hash_(0), - process_id_(0), - trace_options_(kInternalRecordUntilFull), - trace_config_(TraceConfig()), - thread_shared_chunk_index_(0), - generation_(0), - use_worker_thread_(false), - filter_factory_for_testing_(nullptr) { - CategoryRegistry::Initialize(); - -#if defined(OS_NACL) // NaCl shouldn't expose the process id. - SetProcessID(0); -#else - SetProcessID(static_cast(GetCurrentProcId())); -#endif - - logged_events_.reset(CreateTraceBuffer()); - - MemoryDumpManager::GetInstance()->RegisterDumpProvider(this, "TraceLog", - nullptr); -} - -TraceLog::~TraceLog() {} - -void TraceLog::InitializeThreadLocalEventBufferIfSupported() { - // A ThreadLocalEventBuffer needs the message loop - // - to know when the thread exits; - // - to handle the final flush. - // For a thread without a message loop or the message loop may be blocked, the - // trace events will be added into the main buffer directly. - if (thread_blocks_message_loop_.Get() || !MessageLoop::current()) - return; - HEAP_PROFILER_SCOPED_IGNORE; - auto* thread_local_event_buffer = thread_local_event_buffer_.Get(); - if (thread_local_event_buffer && - !CheckGeneration(thread_local_event_buffer->generation())) { - delete thread_local_event_buffer; - thread_local_event_buffer = NULL; - } - if (!thread_local_event_buffer) { - thread_local_event_buffer = new ThreadLocalEventBuffer(this); - thread_local_event_buffer_.Set(thread_local_event_buffer); - } -} - -bool TraceLog::OnMemoryDump(const MemoryDumpArgs& args, - ProcessMemoryDump* pmd) { - // TODO(ssid): Use MemoryDumpArgs to create light dumps when requested - // (crbug.com/499731). - TraceEventMemoryOverhead overhead; - overhead.Add("TraceLog", sizeof(*this)); - { - AutoLock lock(lock_); - if (logged_events_) - logged_events_->EstimateTraceMemoryOverhead(&overhead); - - for (auto& metadata_event : metadata_events_) - metadata_event->EstimateTraceMemoryOverhead(&overhead); - } - overhead.AddSelf(); - overhead.DumpInto("tracing/main_trace_log", pmd); - return true; -} - -const unsigned char* TraceLog::GetCategoryGroupEnabled( - const char* category_group) { - TraceLog* tracelog = GetInstance(); - if (!tracelog) { - DCHECK(!CategoryRegistry::kCategoryAlreadyShutdown->is_enabled()); - return CategoryRegistry::kCategoryAlreadyShutdown->state_ptr(); - } - TraceCategory* category = CategoryRegistry::GetCategoryByName(category_group); - if (!category) { - // Slow path: in the case of a new category we have to repeat the check - // holding the lock, as multiple threads might have reached this point - // at the same time. - auto category_initializer = [](TraceCategory* category) { - TraceLog::GetInstance()->UpdateCategoryState(category); - }; - AutoLock lock(tracelog->lock_); - CategoryRegistry::GetOrCreateCategoryLocked( - category_group, category_initializer, &category); - } - DCHECK(category->state_ptr()); - return category->state_ptr(); -} - -const char* TraceLog::GetCategoryGroupName( - const unsigned char* category_group_enabled) { - return CategoryRegistry::GetCategoryByStatePtr(category_group_enabled) - ->name(); -} - -void TraceLog::UpdateCategoryState(TraceCategory* category) { - lock_.AssertAcquired(); - DCHECK(category->is_valid()); - unsigned char state_flags = 0; - if (enabled_modes_ & RECORDING_MODE && - trace_config_.IsCategoryGroupEnabled(category->name())) { - state_flags |= TraceCategory::ENABLED_FOR_RECORDING; - } - - // TODO(primiano): this is a temporary workaround for catapult:#2341, - // to guarantee that metadata events are always added even if the category - // filter is "-*". See crbug.com/618054 for more details and long-term fix. - if (enabled_modes_ & RECORDING_MODE && - category == CategoryRegistry::kCategoryMetadata) { - state_flags |= TraceCategory::ENABLED_FOR_RECORDING; - } - -#if defined(OS_WIN) - if (base::trace_event::TraceEventETWExport::IsCategoryGroupEnabled( - category->name())) { - state_flags |= TraceCategory::ENABLED_FOR_ETW_EXPORT; - } -#endif - - uint32_t enabled_filters_bitmap = 0; - int index = 0; - for (const auto& event_filter : enabled_event_filters_) { - if (event_filter.IsCategoryGroupEnabled(category->name())) { - state_flags |= TraceCategory::ENABLED_FOR_FILTERING; - DCHECK(GetCategoryGroupFilters()[index]); - enabled_filters_bitmap |= 1 << index; - } - if (index++ >= MAX_TRACE_EVENT_FILTERS) { - NOTREACHED(); - break; - } - } - category->set_enabled_filters(enabled_filters_bitmap); - category->set_state(state_flags); -} - -void TraceLog::UpdateCategoryRegistry() { - lock_.AssertAcquired(); - CreateFiltersForTraceConfig(); - for (TraceCategory& category : CategoryRegistry::GetAllCategories()) { - UpdateCategoryState(&category); - } -} - -void TraceLog::CreateFiltersForTraceConfig() { - if (!(enabled_modes_ & FILTERING_MODE)) - return; - - // Filters were already added and tracing could be enabled. Filters list - // cannot be changed when trace events are using them. - if (GetCategoryGroupFilters().size()) - return; - - for (auto& filter_config : enabled_event_filters_) { - if (GetCategoryGroupFilters().size() >= MAX_TRACE_EVENT_FILTERS) { - NOTREACHED() - << "Too many trace event filters installed in the current session"; - break; - } - - std::unique_ptr new_filter; - const std::string& predicate_name = filter_config.predicate_name(); - if (predicate_name == EventNameFilter::kName) { - auto whitelist = MakeUnique>(); - CHECK(filter_config.GetArgAsSet("event_name_whitelist", &*whitelist)); - new_filter = MakeUnique(std::move(whitelist)); - } else if (predicate_name == HeapProfilerEventFilter::kName) { - new_filter = MakeUnique(); - } else { - if (filter_factory_for_testing_) - new_filter = filter_factory_for_testing_(predicate_name); - CHECK(new_filter) << "Unknown trace filter " << predicate_name; - } - GetCategoryGroupFilters().push_back(std::move(new_filter)); - } -} - -void TraceLog::UpdateSyntheticDelaysFromTraceConfig() { - ResetTraceEventSyntheticDelays(); - const TraceConfig::StringList& delays = - trace_config_.GetSyntheticDelayValues(); - TraceConfig::StringList::const_iterator ci; - for (ci = delays.begin(); ci != delays.end(); ++ci) { - StringTokenizer tokens(*ci, ";"); - if (!tokens.GetNext()) - continue; - TraceEventSyntheticDelay* delay = - TraceEventSyntheticDelay::Lookup(tokens.token()); - while (tokens.GetNext()) { - std::string token = tokens.token(); - char* duration_end; - double target_duration = strtod(token.c_str(), &duration_end); - if (duration_end != token.c_str()) { - delay->SetTargetDuration(TimeDelta::FromMicroseconds( - static_cast(target_duration * 1e6))); - } else if (token == "static") { - delay->SetMode(TraceEventSyntheticDelay::STATIC); - } else if (token == "oneshot") { - delay->SetMode(TraceEventSyntheticDelay::ONE_SHOT); - } else if (token == "alternating") { - delay->SetMode(TraceEventSyntheticDelay::ALTERNATING); - } - } - } -} - -void TraceLog::GetKnownCategoryGroups( - std::vector* category_groups) { - for (const auto& category : CategoryRegistry::GetAllCategories()) { - if (!CategoryRegistry::IsBuiltinCategory(&category)) - category_groups->push_back(category.name()); - } -} - -void TraceLog::SetEnabled(const TraceConfig& trace_config, - uint8_t modes_to_enable) { - std::vector observer_list; - std::map observer_map; - { - AutoLock lock(lock_); - - // Can't enable tracing when Flush() is in progress. - DCHECK(!flush_task_runner_); - - InternalTraceOptions new_options = - GetInternalOptionsFromTraceConfig(trace_config); - - InternalTraceOptions old_options = trace_options(); - - if (dispatching_to_observer_list_) { - // TODO(ssid): Change to NOTREACHED after fixing crbug.com/625170. - DLOG(ERROR) - << "Cannot manipulate TraceLog::Enabled state from an observer."; - return; - } - - // Clear all filters from previous tracing session. These filters are not - // cleared at the end of tracing because some threads which hit trace event - // when disabling, could try to use the filters. - if (!enabled_modes_) - GetCategoryGroupFilters().clear(); - - // Update trace config for recording. - const bool already_recording = enabled_modes_ & RECORDING_MODE; - if (modes_to_enable & RECORDING_MODE) { - if (already_recording) { - // TODO(ssid): Stop suporting enabling of RECODING_MODE when already - // enabled crbug.com/625170. - DCHECK_EQ(new_options, old_options) << "Attempting to re-enable " - "tracing with a different set " - "of options."; - trace_config_.Merge(trace_config); - } else { - trace_config_ = trace_config; - } - } - - // Update event filters. - if (modes_to_enable & FILTERING_MODE) { - DCHECK(!trace_config.event_filters().empty()) - << "Attempting to enable filtering without any filters"; - DCHECK(enabled_event_filters_.empty()) << "Attempting to re-enable " - "filtering when filters are " - "already enabled."; - - // Use the given event filters only if filtering was not enabled. - if (enabled_event_filters_.empty()) - enabled_event_filters_ = trace_config.event_filters(); - } - // Keep the |trace_config_| updated with only enabled filters in case anyone - // tries to read it using |GetCurrentTraceConfig| (even if filters are - // empty). - trace_config_.SetEventFilters(enabled_event_filters_); - - enabled_modes_ |= modes_to_enable; - UpdateCategoryRegistry(); - - // Do not notify observers or create trace buffer if only enabled for - // filtering or if recording was already enabled. - if (!(modes_to_enable & RECORDING_MODE) || already_recording) - return; - - if (new_options != old_options) { - subtle::NoBarrier_Store(&trace_options_, new_options); - UseNextTraceBuffer(); - } - - num_traces_recorded_++; - - UpdateCategoryRegistry(); - UpdateSyntheticDelaysFromTraceConfig(); - - dispatching_to_observer_list_ = true; - observer_list = enabled_state_observer_list_; - observer_map = async_observers_; - } - // Notify observers outside the lock in case they trigger trace events. - for (EnabledStateObserver* observer : observer_list) - observer->OnTraceLogEnabled(); - for (const auto& it : observer_map) { - it.second.task_runner->PostTask( - FROM_HERE, Bind(&AsyncEnabledStateObserver::OnTraceLogEnabled, - it.second.observer)); - } - - { - AutoLock lock(lock_); - dispatching_to_observer_list_ = false; - } -} - -void TraceLog::SetArgumentFilterPredicate( - const ArgumentFilterPredicate& argument_filter_predicate) { - AutoLock lock(lock_); - DCHECK(!argument_filter_predicate.is_null()); - DCHECK(argument_filter_predicate_.is_null()); - argument_filter_predicate_ = argument_filter_predicate; -} - -TraceLog::InternalTraceOptions TraceLog::GetInternalOptionsFromTraceConfig( - const TraceConfig& config) { - InternalTraceOptions ret = config.IsArgumentFilterEnabled() - ? kInternalEnableArgumentFilter - : kInternalNone; - switch (config.GetTraceRecordMode()) { - case RECORD_UNTIL_FULL: - return ret | kInternalRecordUntilFull; - case RECORD_CONTINUOUSLY: - return ret | kInternalRecordContinuously; - case ECHO_TO_CONSOLE: - return ret | kInternalEchoToConsole; - case RECORD_AS_MUCH_AS_POSSIBLE: - return ret | kInternalRecordAsMuchAsPossible; - } - NOTREACHED(); - return kInternalNone; -} - -TraceConfig TraceLog::GetCurrentTraceConfig() const { - AutoLock lock(lock_); - return trace_config_; -} - -void TraceLog::SetDisabled() { - AutoLock lock(lock_); - SetDisabledWhileLocked(RECORDING_MODE); -} - -void TraceLog::SetDisabled(uint8_t modes_to_disable) { - AutoLock lock(lock_); - SetDisabledWhileLocked(modes_to_disable); -} - -void TraceLog::SetDisabledWhileLocked(uint8_t modes_to_disable) { - lock_.AssertAcquired(); - - if (!(enabled_modes_ & modes_to_disable)) - return; - - if (dispatching_to_observer_list_) { - // TODO(ssid): Change to NOTREACHED after fixing crbug.com/625170. - DLOG(ERROR) - << "Cannot manipulate TraceLog::Enabled state from an observer."; - return; - } - - bool is_recording_mode_disabled = - (enabled_modes_ & RECORDING_MODE) && (modes_to_disable & RECORDING_MODE); - enabled_modes_ &= ~modes_to_disable; - - if (modes_to_disable & FILTERING_MODE) - enabled_event_filters_.clear(); - - if (modes_to_disable & RECORDING_MODE) - trace_config_.Clear(); - - UpdateCategoryRegistry(); - - // Add metadata events and notify observers only if recording mode was - // disabled now. - if (!is_recording_mode_disabled) - return; - - AddMetadataEventsWhileLocked(); - - // Remove metadata events so they will not get added to a subsequent trace. - metadata_events_.clear(); - - dispatching_to_observer_list_ = true; - std::vector observer_list = - enabled_state_observer_list_; - std::map observer_map = - async_observers_; - - { - // Dispatch to observers outside the lock in case the observer triggers a - // trace event. - AutoUnlock unlock(lock_); - for (EnabledStateObserver* observer : observer_list) - observer->OnTraceLogDisabled(); - for (const auto& it : observer_map) { - it.second.task_runner->PostTask( - FROM_HERE, Bind(&AsyncEnabledStateObserver::OnTraceLogDisabled, - it.second.observer)); - } - } - dispatching_to_observer_list_ = false; -} - -int TraceLog::GetNumTracesRecorded() { - AutoLock lock(lock_); - if (!IsEnabled()) - return -1; - return num_traces_recorded_; -} - -void TraceLog::AddEnabledStateObserver(EnabledStateObserver* listener) { - AutoLock lock(lock_); - enabled_state_observer_list_.push_back(listener); -} - -void TraceLog::RemoveEnabledStateObserver(EnabledStateObserver* listener) { - AutoLock lock(lock_); - std::vector::iterator it = - std::find(enabled_state_observer_list_.begin(), - enabled_state_observer_list_.end(), listener); - if (it != enabled_state_observer_list_.end()) - enabled_state_observer_list_.erase(it); -} - -bool TraceLog::HasEnabledStateObserver(EnabledStateObserver* listener) const { - AutoLock lock(lock_); - return ContainsValue(enabled_state_observer_list_, listener); -} - -TraceLogStatus TraceLog::GetStatus() const { - AutoLock lock(lock_); - TraceLogStatus result; - result.event_capacity = static_cast(logged_events_->Capacity()); - result.event_count = static_cast(logged_events_->Size()); - return result; -} - -bool TraceLog::BufferIsFull() const { - AutoLock lock(lock_); - return logged_events_->IsFull(); -} - -TraceEvent* TraceLog::AddEventToThreadSharedChunkWhileLocked( - TraceEventHandle* handle, - bool check_buffer_is_full) { - lock_.AssertAcquired(); - - if (thread_shared_chunk_ && thread_shared_chunk_->IsFull()) { - logged_events_->ReturnChunk(thread_shared_chunk_index_, - std::move(thread_shared_chunk_)); - } - - if (!thread_shared_chunk_) { - thread_shared_chunk_ = - logged_events_->GetChunk(&thread_shared_chunk_index_); - if (check_buffer_is_full) - CheckIfBufferIsFullWhileLocked(); - } - if (!thread_shared_chunk_) - return NULL; - - size_t event_index; - TraceEvent* trace_event = thread_shared_chunk_->AddTraceEvent(&event_index); - if (trace_event && handle) { - MakeHandle(thread_shared_chunk_->seq(), thread_shared_chunk_index_, - event_index, handle); - } - return trace_event; -} - -void TraceLog::CheckIfBufferIsFullWhileLocked() { - lock_.AssertAcquired(); - if (logged_events_->IsFull()) { - if (buffer_limit_reached_timestamp_.is_null()) { - buffer_limit_reached_timestamp_ = OffsetNow(); - } - SetDisabledWhileLocked(RECORDING_MODE); - } -} - -// Flush() works as the following: -// 1. Flush() is called in thread A whose task runner is saved in -// flush_task_runner_; -// 2. If thread_message_loops_ is not empty, thread A posts task to each message -// loop to flush the thread local buffers; otherwise finish the flush; -// 3. FlushCurrentThread() deletes the thread local event buffer: -// - The last batch of events of the thread are flushed into the main buffer; -// - The message loop will be removed from thread_message_loops_; -// If this is the last message loop, finish the flush; -// 4. If any thread hasn't finish its flush in time, finish the flush. -void TraceLog::Flush(const TraceLog::OutputCallback& cb, - bool use_worker_thread) { - FlushInternal(cb, use_worker_thread, false); -} - -void TraceLog::CancelTracing(const OutputCallback& cb) { - SetDisabled(); - FlushInternal(cb, false, true); -} - -void TraceLog::FlushInternal(const TraceLog::OutputCallback& cb, - bool use_worker_thread, - bool discard_events) { - use_worker_thread_ = use_worker_thread; - if (IsEnabled()) { - // Can't flush when tracing is enabled because otherwise PostTask would - // - generate more trace events; - // - deschedule the calling thread on some platforms causing inaccurate - // timing of the trace events. - scoped_refptr empty_result = new RefCountedString; - if (!cb.is_null()) - cb.Run(empty_result, false); - LOG(WARNING) << "Ignored TraceLog::Flush called when tracing is enabled"; - return; - } - - int gen = generation(); - // Copy of thread_message_loops_ to be used without locking. - std::vector> - thread_message_loop_task_runners; - { - AutoLock lock(lock_); - DCHECK(!flush_task_runner_); - flush_task_runner_ = ThreadTaskRunnerHandle::IsSet() - ? ThreadTaskRunnerHandle::Get() - : nullptr; - DCHECK(thread_message_loops_.empty() || flush_task_runner_); - flush_output_callback_ = cb; - - if (thread_shared_chunk_) { - logged_events_->ReturnChunk(thread_shared_chunk_index_, - std::move(thread_shared_chunk_)); - } - - for (MessageLoop* loop : thread_message_loops_) - thread_message_loop_task_runners.push_back(loop->task_runner()); - } - - if (!thread_message_loop_task_runners.empty()) { - for (auto& task_runner : thread_message_loop_task_runners) { - task_runner->PostTask( - FROM_HERE, Bind(&TraceLog::FlushCurrentThread, Unretained(this), - gen, discard_events)); - } - flush_task_runner_->PostDelayedTask( - FROM_HERE, Bind(&TraceLog::OnFlushTimeout, Unretained(this), gen, - discard_events), - TimeDelta::FromMilliseconds(kThreadFlushTimeoutMs)); - return; - } - - FinishFlush(gen, discard_events); -} - -// Usually it runs on a different thread. -void TraceLog::ConvertTraceEventsToTraceFormat( - std::unique_ptr logged_events, - const OutputCallback& flush_output_callback, - const ArgumentFilterPredicate& argument_filter_predicate) { - if (flush_output_callback.is_null()) - return; - - HEAP_PROFILER_SCOPED_IGNORE; - // The callback need to be called at least once even if there is no events - // to let the caller know the completion of flush. - scoped_refptr json_events_str_ptr = new RefCountedString(); - while (const TraceBufferChunk* chunk = logged_events->NextChunk()) { - for (size_t j = 0; j < chunk->size(); ++j) { - size_t size = json_events_str_ptr->size(); - if (size > kTraceEventBufferSizeInBytes) { - flush_output_callback.Run(json_events_str_ptr, true); - json_events_str_ptr = new RefCountedString(); - } else if (size) { - json_events_str_ptr->data().append(",\n"); - } - chunk->GetEventAt(j)->AppendAsJSON(&(json_events_str_ptr->data()), - argument_filter_predicate); - } - } - flush_output_callback.Run(json_events_str_ptr, false); -} - -void TraceLog::FinishFlush(int generation, bool discard_events) { - std::unique_ptr previous_logged_events; - OutputCallback flush_output_callback; - ArgumentFilterPredicate argument_filter_predicate; - - if (!CheckGeneration(generation)) - return; - - { - AutoLock lock(lock_); - - previous_logged_events.swap(logged_events_); - UseNextTraceBuffer(); - thread_message_loops_.clear(); - - flush_task_runner_ = NULL; - flush_output_callback = flush_output_callback_; - flush_output_callback_.Reset(); - - if (trace_options() & kInternalEnableArgumentFilter) { - CHECK(!argument_filter_predicate_.is_null()); - argument_filter_predicate = argument_filter_predicate_; - } - } - - if (discard_events) { - if (!flush_output_callback.is_null()) { - scoped_refptr empty_result = new RefCountedString; - flush_output_callback.Run(empty_result, false); - } - return; - } - - if (use_worker_thread_) { -#if 0 - base::PostTaskWithTraits( - FROM_HERE, base::TaskTraits() - .MayBlock() - .WithPriority(base::TaskPriority::BACKGROUND) - .WithShutdownBehavior( - base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN), - Bind(&TraceLog::ConvertTraceEventsToTraceFormat, - Passed(&previous_logged_events), flush_output_callback, - argument_filter_predicate)); - return; -#else - NOTREACHED(); -#endif - } - - ConvertTraceEventsToTraceFormat(std::move(previous_logged_events), - flush_output_callback, - argument_filter_predicate); -} - -// Run in each thread holding a local event buffer. -void TraceLog::FlushCurrentThread(int generation, bool discard_events) { - { - AutoLock lock(lock_); - if (!CheckGeneration(generation) || !flush_task_runner_) { - // This is late. The corresponding flush has finished. - return; - } - } - - // This will flush the thread local buffer. - delete thread_local_event_buffer_.Get(); - - AutoLock lock(lock_); - if (!CheckGeneration(generation) || !flush_task_runner_ || - !thread_message_loops_.empty()) - return; - - flush_task_runner_->PostTask( - FROM_HERE, Bind(&TraceLog::FinishFlush, Unretained(this), generation, - discard_events)); -} - -void TraceLog::OnFlushTimeout(int generation, bool discard_events) { - { - AutoLock lock(lock_); - if (!CheckGeneration(generation) || !flush_task_runner_) { - // Flush has finished before timeout. - return; - } - - LOG(WARNING) - << "The following threads haven't finished flush in time. " - "If this happens stably for some thread, please call " - "TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop() from " - "the thread to avoid its trace events from being lost."; - for (hash_set::const_iterator it = - thread_message_loops_.begin(); - it != thread_message_loops_.end(); ++it) { - LOG(WARNING) << "Thread: " << (*it)->GetThreadName(); - } - } - FinishFlush(generation, discard_events); -} - -void TraceLog::UseNextTraceBuffer() { - logged_events_.reset(CreateTraceBuffer()); - subtle::NoBarrier_AtomicIncrement(&generation_, 1); - thread_shared_chunk_.reset(); - thread_shared_chunk_index_ = 0; -} - -TraceEventHandle TraceLog::AddTraceEvent( - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags) { - int thread_id = static_cast(base::PlatformThread::CurrentId()); - base::TimeTicks now = base::TimeTicks::Now(); - return AddTraceEventWithThreadIdAndTimestamp( - phase, - category_group_enabled, - name, - scope, - id, - trace_event_internal::kNoId, // bind_id - thread_id, - now, - num_args, - arg_names, - arg_types, - arg_values, - convertable_values, - flags); -} - -TraceEventHandle TraceLog::AddTraceEventWithBindId( - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - unsigned long long bind_id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags) { - int thread_id = static_cast(base::PlatformThread::CurrentId()); - base::TimeTicks now = base::TimeTicks::Now(); - return AddTraceEventWithThreadIdAndTimestamp( - phase, - category_group_enabled, - name, - scope, - id, - bind_id, - thread_id, - now, - num_args, - arg_names, - arg_types, - arg_values, - convertable_values, - flags | TRACE_EVENT_FLAG_HAS_CONTEXT_ID); -} - -TraceEventHandle TraceLog::AddTraceEventWithProcessId( - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - int process_id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags) { - base::TimeTicks now = base::TimeTicks::Now(); - return AddTraceEventWithThreadIdAndTimestamp( - phase, - category_group_enabled, - name, - scope, - id, - trace_event_internal::kNoId, // bind_id - process_id, - now, - num_args, - arg_names, - arg_types, - arg_values, - convertable_values, - flags | TRACE_EVENT_FLAG_HAS_PROCESS_ID); -} - -// Handle legacy calls to AddTraceEventWithThreadIdAndTimestamp -// with kNoId as bind_id -TraceEventHandle TraceLog::AddTraceEventWithThreadIdAndTimestamp( - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - int thread_id, - const TimeTicks& timestamp, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags) { - return AddTraceEventWithThreadIdAndTimestamp( - phase, - category_group_enabled, - name, - scope, - id, - trace_event_internal::kNoId, // bind_id - thread_id, - timestamp, - num_args, - arg_names, - arg_types, - arg_values, - convertable_values, - flags); -} - -TraceEventHandle TraceLog::AddTraceEventWithThreadIdAndTimestamp( - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - unsigned long long bind_id, - int thread_id, - const TimeTicks& timestamp, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags) { - TraceEventHandle handle = {0, 0, 0}; - if (!*category_group_enabled) - return handle; - - // Avoid re-entrance of AddTraceEvent. This may happen in GPU process when - // ECHO_TO_CONSOLE is enabled: AddTraceEvent -> LOG(ERROR) -> - // GpuProcessLogMessageHandler -> PostPendingTask -> TRACE_EVENT ... - if (thread_is_in_trace_event_.Get()) - return handle; - - AutoThreadLocalBoolean thread_is_in_trace_event(&thread_is_in_trace_event_); - - DCHECK(name); - DCHECK(!timestamp.is_null()); - - if (flags & TRACE_EVENT_FLAG_MANGLE_ID) { - if ((flags & TRACE_EVENT_FLAG_FLOW_IN) || - (flags & TRACE_EVENT_FLAG_FLOW_OUT)) - bind_id = MangleEventId(bind_id); - id = MangleEventId(id); - } - - TimeTicks offset_event_timestamp = OffsetTimestamp(timestamp); - ThreadTicks thread_now = ThreadNow(); - - ThreadLocalEventBuffer* thread_local_event_buffer = nullptr; - if (*category_group_enabled & RECORDING_MODE) { - // |thread_local_event_buffer_| can be null if the current thread doesn't - // have a message loop or the message loop is blocked. - InitializeThreadLocalEventBufferIfSupported(); - thread_local_event_buffer = thread_local_event_buffer_.Get(); - } - - // Check and update the current thread name only if the event is for the - // current thread to avoid locks in most cases. - if (thread_id == static_cast(PlatformThread::CurrentId())) { - const char* new_name = - ThreadIdNameManager::GetInstance()->GetName(thread_id); - // Check if the thread name has been set or changed since the previous - // call (if any), but don't bother if the new name is empty. Note this will - // not detect a thread name change within the same char* buffer address: we - // favor common case performance over corner case correctness. - static auto* current_thread_name = new ThreadLocalPointer(); - if (new_name != current_thread_name->Get() && new_name && *new_name) { - current_thread_name->Set(new_name); - - AutoLock thread_info_lock(thread_info_lock_); - - hash_map::iterator existing_name = - thread_names_.find(thread_id); - if (existing_name == thread_names_.end()) { - // This is a new thread id, and a new name. - thread_names_[thread_id] = new_name; - } else { - // This is a thread id that we've seen before, but potentially with a - // new name. - std::vector existing_names = base::SplitStringPiece( - existing_name->second, ",", base::KEEP_WHITESPACE, - base::SPLIT_WANT_NONEMPTY); - bool found = std::find(existing_names.begin(), existing_names.end(), - new_name) != existing_names.end(); - if (!found) { - if (!existing_names.empty()) - existing_name->second.push_back(','); - existing_name->second.append(new_name); - } - } - } - } - -#if defined(OS_WIN) - // This is done sooner rather than later, to avoid creating the event and - // acquiring the lock, which is not needed for ETW as it's already threadsafe. - if (*category_group_enabled & TraceCategory::ENABLED_FOR_ETW_EXPORT) - TraceEventETWExport::AddEvent(phase, category_group_enabled, name, id, - num_args, arg_names, arg_types, arg_values, - convertable_values); -#endif // OS_WIN - - std::string console_message; - std::unique_ptr filtered_trace_event; - bool disabled_by_filters = false; - if (*category_group_enabled & TraceCategory::ENABLED_FOR_FILTERING) { - std::unique_ptr new_trace_event(new TraceEvent); - new_trace_event->Initialize(thread_id, offset_event_timestamp, thread_now, - phase, category_group_enabled, name, scope, id, - bind_id, num_args, arg_names, arg_types, - arg_values, convertable_values, flags); - - disabled_by_filters = true; - ForEachCategoryFilter( - category_group_enabled, [&new_trace_event, &disabled_by_filters]( - TraceEventFilter* trace_event_filter) { - if (trace_event_filter->FilterTraceEvent(*new_trace_event)) - disabled_by_filters = false; - }); - if (!disabled_by_filters) - filtered_trace_event = std::move(new_trace_event); - } - - // If enabled for recording, the event should be added only if one of the - // filters indicates or category is not enabled for filtering. - if ((*category_group_enabled & TraceCategory::ENABLED_FOR_RECORDING) && - !disabled_by_filters) { - OptionalAutoLock lock(&lock_); - - TraceEvent* trace_event = NULL; - if (thread_local_event_buffer) { - trace_event = thread_local_event_buffer->AddTraceEvent(&handle); - } else { - lock.EnsureAcquired(); - trace_event = AddEventToThreadSharedChunkWhileLocked(&handle, true); - } - - if (trace_event) { - if (filtered_trace_event) { - trace_event->MoveFrom(std::move(filtered_trace_event)); - } else { - trace_event->Initialize(thread_id, offset_event_timestamp, thread_now, - phase, category_group_enabled, name, scope, id, - bind_id, num_args, arg_names, arg_types, - arg_values, convertable_values, flags); - } - -#if defined(OS_ANDROID) - trace_event->SendToATrace(); -#endif - } - - if (trace_options() & kInternalEchoToConsole) { - console_message = EventToConsoleMessage( - phase == TRACE_EVENT_PHASE_COMPLETE ? TRACE_EVENT_PHASE_BEGIN : phase, - timestamp, trace_event); - } - } - - if (!console_message.empty()) - LOG(ERROR) << console_message; - - return handle; -} - -void TraceLog::AddMetadataEvent( - const unsigned char* category_group_enabled, - const char* name, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags) { - HEAP_PROFILER_SCOPED_IGNORE; - std::unique_ptr trace_event(new TraceEvent); - int thread_id = static_cast(base::PlatformThread::CurrentId()); - ThreadTicks thread_now = ThreadNow(); - TimeTicks now = OffsetNow(); - AutoLock lock(lock_); - trace_event->Initialize( - thread_id, now, thread_now, TRACE_EVENT_PHASE_METADATA, - category_group_enabled, name, - trace_event_internal::kGlobalScope, // scope - trace_event_internal::kNoId, // id - trace_event_internal::kNoId, // bind_id - num_args, arg_names, arg_types, arg_values, convertable_values, flags); - metadata_events_.push_back(std::move(trace_event)); -} - -// May be called when a COMPELETE event ends and the unfinished event has been -// recycled (phase == TRACE_EVENT_PHASE_END and trace_event == NULL). -std::string TraceLog::EventToConsoleMessage(unsigned char phase, - const TimeTicks& timestamp, - TraceEvent* trace_event) { - HEAP_PROFILER_SCOPED_IGNORE; - AutoLock thread_info_lock(thread_info_lock_); - - // The caller should translate TRACE_EVENT_PHASE_COMPLETE to - // TRACE_EVENT_PHASE_BEGIN or TRACE_EVENT_END. - DCHECK(phase != TRACE_EVENT_PHASE_COMPLETE); - - TimeDelta duration; - int thread_id = - trace_event ? trace_event->thread_id() : PlatformThread::CurrentId(); - if (phase == TRACE_EVENT_PHASE_END) { - duration = timestamp - thread_event_start_times_[thread_id].top(); - thread_event_start_times_[thread_id].pop(); - } - - std::string thread_name = thread_names_[thread_id]; - if (thread_colors_.find(thread_name) == thread_colors_.end()) - thread_colors_[thread_name] = (thread_colors_.size() % 6) + 1; - - std::ostringstream log; - log << base::StringPrintf("%s: \x1b[0;3%dm", thread_name.c_str(), - thread_colors_[thread_name]); - - size_t depth = 0; - auto it = thread_event_start_times_.find(thread_id); - if (it != thread_event_start_times_.end()) - depth = it->second.size(); - - for (size_t i = 0; i < depth; ++i) - log << "| "; - - if (trace_event) - trace_event->AppendPrettyPrinted(&log); - if (phase == TRACE_EVENT_PHASE_END) - log << base::StringPrintf(" (%.3f ms)", duration.InMillisecondsF()); - - log << "\x1b[0;m"; - - if (phase == TRACE_EVENT_PHASE_BEGIN) - thread_event_start_times_[thread_id].push(timestamp); - - return log.str(); -} - -void TraceLog::EndFilteredEvent(const unsigned char* category_group_enabled, - const char* name, - TraceEventHandle handle) { - const char* category_name = GetCategoryGroupName(category_group_enabled); - ForEachCategoryFilter( - category_group_enabled, - [name, category_name](TraceEventFilter* trace_event_filter) { - trace_event_filter->EndEvent(category_name, name); - }); -} - -void TraceLog::UpdateTraceEventDuration( - const unsigned char* category_group_enabled, - const char* name, - TraceEventHandle handle) { - char category_group_enabled_local = *category_group_enabled; - if (!category_group_enabled_local) - return; - - // Avoid re-entrance of AddTraceEvent. This may happen in GPU process when - // ECHO_TO_CONSOLE is enabled: AddTraceEvent -> LOG(ERROR) -> - // GpuProcessLogMessageHandler -> PostPendingTask -> TRACE_EVENT ... - if (thread_is_in_trace_event_.Get()) - return; - - AutoThreadLocalBoolean thread_is_in_trace_event(&thread_is_in_trace_event_); - - ThreadTicks thread_now = ThreadNow(); - TimeTicks now = OffsetNow(); - -#if defined(OS_WIN) - // Generate an ETW event that marks the end of a complete event. - if (category_group_enabled_local & TraceCategory::ENABLED_FOR_ETW_EXPORT) - TraceEventETWExport::AddCompleteEndEvent(name); -#endif // OS_WIN - - std::string console_message; - if (category_group_enabled_local & TraceCategory::ENABLED_FOR_RECORDING) { - OptionalAutoLock lock(&lock_); - - TraceEvent* trace_event = GetEventByHandleInternal(handle, &lock); - if (trace_event) { - DCHECK(trace_event->phase() == TRACE_EVENT_PHASE_COMPLETE); - // TEMP(oysteine) to debug crbug.com/638744 - if (trace_event->duration().ToInternalValue() != -1) { - DVLOG(1) << "TraceHandle: chunk_seq " << handle.chunk_seq - << ", chunk_index " << handle.chunk_index << ", event_index " - << handle.event_index; - - std::string serialized_event; - trace_event->AppendAsJSON(&serialized_event, ArgumentFilterPredicate()); - DVLOG(1) << "TraceEvent: " << serialized_event; - lock_.AssertAcquired(); - } - - trace_event->UpdateDuration(now, thread_now); -#if defined(OS_ANDROID) - trace_event->SendToATrace(); -#endif - } - - if (trace_options() & kInternalEchoToConsole) { - console_message = - EventToConsoleMessage(TRACE_EVENT_PHASE_END, now, trace_event); - } - } - - if (!console_message.empty()) - LOG(ERROR) << console_message; - - if (category_group_enabled_local & TraceCategory::ENABLED_FOR_FILTERING) - EndFilteredEvent(category_group_enabled, name, handle); -} - -uint64_t TraceLog::MangleEventId(uint64_t id) { - return id ^ process_id_hash_; -} - -void TraceLog::AddMetadataEventsWhileLocked() { - lock_.AssertAcquired(); - - // Move metadata added by |AddMetadataEvent| into the trace log. - while (!metadata_events_.empty()) { - TraceEvent* event = AddEventToThreadSharedChunkWhileLocked(nullptr, false); - event->MoveFrom(std::move(metadata_events_.back())); - metadata_events_.pop_back(); - } - -#if !defined(OS_NACL) // NaCl shouldn't expose the process id. - InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), - 0, "num_cpus", "number", - base::SysInfo::NumberOfProcessors()); -#endif - - int current_thread_id = static_cast(base::PlatformThread::CurrentId()); - if (process_sort_index_ != 0) { - InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), - current_thread_id, "process_sort_index", - "sort_index", process_sort_index_); - } - - if (!process_name_.empty()) { - InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), - current_thread_id, "process_name", "name", - process_name_); - } - -#if !defined(OS_NACL) && !defined(OS_IOS) - Time process_creation_time = CurrentProcessInfo::CreationTime(); - if (!process_creation_time.is_null()) { - TimeDelta process_uptime = Time::Now() - process_creation_time; - InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), - current_thread_id, "process_uptime_seconds", - "uptime", process_uptime.InSeconds()); - } -#endif // !defined(OS_NACL) && !defined(OS_IOS) - - if (!process_labels_.empty()) { - std::vector labels; - for (const auto& it : process_labels_) - labels.push_back(it.second); - InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), - current_thread_id, "process_labels", "labels", - base::JoinString(labels, ",")); - } - - // Thread sort indices. - for (const auto& it : thread_sort_indices_) { - if (it.second == 0) - continue; - InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), - it.first, "thread_sort_index", "sort_index", - it.second); - } - - // Thread names. - AutoLock thread_info_lock(thread_info_lock_); - for (const auto& it : thread_names_) { - if (it.second.empty()) - continue; - InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), - it.first, "thread_name", "name", it.second); - } - - // If buffer is full, add a metadata record to report this. - if (!buffer_limit_reached_timestamp_.is_null()) { - InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), - current_thread_id, "trace_buffer_overflowed", - "overflowed_at_ts", - buffer_limit_reached_timestamp_); - } -} - -void TraceLog::DeleteForTesting() { - internal::DeleteTraceLogForTesting::Delete(); - CategoryRegistry::ResetForTesting(); -} - -TraceEvent* TraceLog::GetEventByHandle(TraceEventHandle handle) { - return GetEventByHandleInternal(handle, NULL); -} - -TraceEvent* TraceLog::GetEventByHandleInternal(TraceEventHandle handle, - OptionalAutoLock* lock) { - if (!handle.chunk_seq) - return NULL; - - DCHECK(handle.chunk_seq); - DCHECK(handle.chunk_index <= TraceBufferChunk::kMaxChunkIndex); - DCHECK(handle.event_index < TraceBufferChunk::kTraceBufferChunkSize); - - if (thread_local_event_buffer_.Get()) { - TraceEvent* trace_event = - thread_local_event_buffer_.Get()->GetEventByHandle(handle); - if (trace_event) - return trace_event; - } - - // The event has been out-of-control of the thread local buffer. - // Try to get the event from the main buffer with a lock. - if (lock) - lock->EnsureAcquired(); - - if (thread_shared_chunk_ && - handle.chunk_index == thread_shared_chunk_index_) { - return handle.chunk_seq == thread_shared_chunk_->seq() - ? thread_shared_chunk_->GetEventAt(handle.event_index) - : NULL; - } - - return logged_events_->GetEventByHandle(handle); -} - -void TraceLog::SetProcessID(int process_id) { - process_id_ = process_id; - // Create a FNV hash from the process ID for XORing. - // See http://isthe.com/chongo/tech/comp/fnv/ for algorithm details. - const unsigned long long kOffsetBasis = 14695981039346656037ull; - const unsigned long long kFnvPrime = 1099511628211ull; - const unsigned long long pid = static_cast(process_id_); - process_id_hash_ = (kOffsetBasis ^ pid) * kFnvPrime; -} - -void TraceLog::SetProcessSortIndex(int sort_index) { - AutoLock lock(lock_); - process_sort_index_ = sort_index; -} - -void TraceLog::SetProcessName(const char* process_name) { - AutoLock lock(lock_); - process_name_ = process_name; -} - -void TraceLog::UpdateProcessLabel(int label_id, - const std::string& current_label) { - if (!current_label.length()) - return RemoveProcessLabel(label_id); - - AutoLock lock(lock_); - process_labels_[label_id] = current_label; -} - -void TraceLog::RemoveProcessLabel(int label_id) { - AutoLock lock(lock_); - process_labels_.erase(label_id); -} - -void TraceLog::SetThreadSortIndex(PlatformThreadId thread_id, int sort_index) { - AutoLock lock(lock_); - thread_sort_indices_[static_cast(thread_id)] = sort_index; -} - -void TraceLog::SetTimeOffset(TimeDelta offset) { - time_offset_ = offset; -} - -size_t TraceLog::GetObserverCountForTest() const { - return enabled_state_observer_list_.size(); -} - -void TraceLog::SetCurrentThreadBlocksMessageLoop() { - thread_blocks_message_loop_.Set(true); - // This will flush the thread local buffer. - delete thread_local_event_buffer_.Get(); -} - -TraceBuffer* TraceLog::CreateTraceBuffer() { - HEAP_PROFILER_SCOPED_IGNORE; - InternalTraceOptions options = trace_options(); - if (options & kInternalRecordContinuously) { - return TraceBuffer::CreateTraceBufferRingBuffer( - kTraceEventRingBufferChunks); - } - if (options & kInternalEchoToConsole) { - return TraceBuffer::CreateTraceBufferRingBuffer( - kEchoToConsoleTraceEventBufferChunks); - } - if (options & kInternalRecordAsMuchAsPossible) { - return TraceBuffer::CreateTraceBufferVectorOfSize( - kTraceEventVectorBigBufferChunks); - } - return TraceBuffer::CreateTraceBufferVectorOfSize( - kTraceEventVectorBufferChunks); -} - -#if defined(OS_WIN) -void TraceLog::UpdateETWCategoryGroupEnabledFlags() { - // Go through each category and set/clear the ETW bit depending on whether the - // category is enabled. - for (TraceCategory& category : CategoryRegistry::GetAllCategories()) { - if (base::trace_event::TraceEventETWExport::IsCategoryGroupEnabled( - category.name())) { - category.set_state_flag(TraceCategory::ENABLED_FOR_ETW_EXPORT); - } else { - category.clear_state_flag(TraceCategory::ENABLED_FOR_ETW_EXPORT); - } - } -} -#endif // defined(OS_WIN) - -void ConvertableToTraceFormat::EstimateTraceMemoryOverhead( - TraceEventMemoryOverhead* overhead) { - overhead->Add("ConvertableToTraceFormat(Unknown)", sizeof(*this)); -} - -void TraceLog::AddAsyncEnabledStateObserver( - WeakPtr listener) { - AutoLock lock(lock_); - async_observers_.insert( - std::make_pair(listener.get(), RegisteredAsyncObserver(listener))); -} - -void TraceLog::RemoveAsyncEnabledStateObserver( - AsyncEnabledStateObserver* listener) { - AutoLock lock(lock_); - async_observers_.erase(listener); -} - -bool TraceLog::HasAsyncEnabledStateObserver( - AsyncEnabledStateObserver* listener) const { - AutoLock lock(lock_); - return ContainsKey(async_observers_, listener); -} - -} // namespace trace_event -} // namespace base - -namespace trace_event_internal { - -ScopedTraceBinaryEfficient::ScopedTraceBinaryEfficient( - const char* category_group, - const char* name) { - // The single atom works because for now the category_group can only be "gpu". - DCHECK_EQ(strcmp(category_group, "gpu"), 0); - static TRACE_EVENT_API_ATOMIC_WORD atomic = 0; - INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES( - category_group, atomic, category_group_enabled_); - name_ = name; - if (*category_group_enabled_) { - event_handle_ = - TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( - TRACE_EVENT_PHASE_COMPLETE, - category_group_enabled_, - name, - trace_event_internal::kGlobalScope, // scope - trace_event_internal::kNoId, // id - static_cast(base::PlatformThread::CurrentId()), // thread_id - base::TimeTicks::Now(), - trace_event_internal::kZeroNumArgs, - nullptr, - nullptr, - nullptr, - nullptr, - TRACE_EVENT_FLAG_NONE); - } -} - -ScopedTraceBinaryEfficient::~ScopedTraceBinaryEfficient() { - if (*category_group_enabled_) { - TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION(category_group_enabled_, name_, - event_handle_); - } -} - -} // namespace trace_event_internal diff --git a/base/trace_event/trace_log.h b/base/trace_event/trace_log.h deleted file mode 100644 index 88b6e588e406e70c064ebaea09918033354e15fc..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_log.h +++ /dev/null @@ -1,507 +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. - -#ifndef BASE_TRACE_EVENT_TRACE_LOG_H_ -#define BASE_TRACE_EVENT_TRACE_LOG_H_ - -#include -#include - -#include -#include -#include - -#include "base/atomicops.h" -#include "base/containers/hash_tables.h" -#include "base/gtest_prod_util.h" -#include "base/macros.h" -#include "base/memory/scoped_vector.h" -#include "base/trace_event/memory_dump_provider.h" -#include "base/trace_event/trace_config.h" -#include "base/trace_event/trace_event_impl.h" -#include "build/build_config.h" - -namespace base { - -template -struct DefaultSingletonTraits; -class MessageLoop; -class RefCountedString; - -namespace trace_event { - -struct TraceCategory; -class TraceBuffer; -class TraceBufferChunk; -class TraceEvent; -class TraceEventFilter; -class TraceEventMemoryOverhead; - -struct BASE_EXPORT TraceLogStatus { - TraceLogStatus(); - ~TraceLogStatus(); - uint32_t event_capacity; - uint32_t event_count; -}; - -class BASE_EXPORT TraceLog : public MemoryDumpProvider { - public: - // Argument passed to TraceLog::SetEnabled. - enum Mode : uint8_t { - // Enables normal tracing (recording trace events in the trace buffer). - RECORDING_MODE = 1 << 0, - - // Trace events are enabled just for filtering but not for recording. Only - // event filters config of |trace_config| argument is used. - FILTERING_MODE = 1 << 1 - }; - - static TraceLog* GetInstance(); - - // Get set of known category groups. This can change as new code paths are - // reached. The known category groups are inserted into |category_groups|. - void GetKnownCategoryGroups(std::vector* category_groups); - - // Retrieves a copy (for thread-safety) of the current TraceConfig. - TraceConfig GetCurrentTraceConfig() const; - - // Initializes the thread-local event buffer, if not already initialized and - // if the current thread supports that (has a message loop). - void InitializeThreadLocalEventBufferIfSupported(); - - // See TraceConfig comments for details on how to control which categories - // will be traced. SetDisabled must be called distinctly for each mode that is - // enabled. If tracing has already been enabled for recording, category filter - // (enabled and disabled categories) will be merged into the current category - // filter. Enabling RECORDING_MODE does not enable filters. Trace event - // filters will be used only if FILTERING_MODE is set on |modes_to_enable|. - // Conversely to RECORDING_MODE, FILTERING_MODE doesn't support upgrading, - // i.e. filters can only be enabled if not previously enabled. - void SetEnabled(const TraceConfig& trace_config, uint8_t modes_to_enable); - - // TODO(ssid): Remove the default SetEnabled and IsEnabled. They should take - // Mode as argument. - - // Disables tracing for all categories for the specified |modes_to_disable| - // only. Only RECORDING_MODE is taken as default |modes_to_disable|. - void SetDisabled(); - void SetDisabled(uint8_t modes_to_disable); - - // Returns true if TraceLog is enabled on recording mode. - // Note: Returns false even if FILTERING_MODE is enabled. - bool IsEnabled() { return enabled_modes_ & RECORDING_MODE; } - - // Returns a bitmap of enabled modes from TraceLog::Mode. - uint8_t enabled_modes() { return enabled_modes_; } - - // The number of times we have begun recording traces. If tracing is off, - // returns -1. If tracing is on, then it returns the number of times we have - // recorded a trace. By watching for this number to increment, you can - // passively discover when a new trace has begun. This is then used to - // implement the TRACE_EVENT_IS_NEW_TRACE() primitive. - int GetNumTracesRecorded(); - -#if defined(OS_ANDROID) - void StartATrace(); - void StopATrace(); - void AddClockSyncMetadataEvent(); -#endif - - // Enabled state listeners give a callback when tracing is enabled or - // disabled. This can be used to tie into other library's tracing systems - // on-demand. - class BASE_EXPORT EnabledStateObserver { - public: - virtual ~EnabledStateObserver() = default; - - // Called just after the tracing system becomes enabled, outside of the - // |lock_|. TraceLog::IsEnabled() is true at this point. - virtual void OnTraceLogEnabled() = 0; - - // Called just after the tracing system disables, outside of the |lock_|. - // TraceLog::IsEnabled() is false at this point. - virtual void OnTraceLogDisabled() = 0; - }; - void AddEnabledStateObserver(EnabledStateObserver* listener); - void RemoveEnabledStateObserver(EnabledStateObserver* listener); - bool HasEnabledStateObserver(EnabledStateObserver* listener) const; - - // Asynchronous enabled state listeners. When tracing is enabled or disabled, - // for each observer, a task for invoking its appropriate callback is posted - // to the thread from which AddAsyncEnabledStateObserver() was called. This - // allows the observer to be safely destroyed, provided that it happens on the - // same thread that invoked AddAsyncEnabledStateObserver(). - class BASE_EXPORT AsyncEnabledStateObserver { - public: - virtual ~AsyncEnabledStateObserver() = default; - - // Posted just after the tracing system becomes enabled, outside |lock_|. - // TraceLog::IsEnabled() is true at this point. - virtual void OnTraceLogEnabled() = 0; - - // Posted just after the tracing system becomes disabled, outside |lock_|. - // TraceLog::IsEnabled() is false at this point. - virtual void OnTraceLogDisabled() = 0; - }; - void AddAsyncEnabledStateObserver( - WeakPtr listener); - void RemoveAsyncEnabledStateObserver(AsyncEnabledStateObserver* listener); - bool HasAsyncEnabledStateObserver(AsyncEnabledStateObserver* listener) const; - - TraceLogStatus GetStatus() const; - bool BufferIsFull() const; - - // Computes an estimate of the size of the TraceLog including all the retained - // objects. - void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead); - - void SetArgumentFilterPredicate( - const ArgumentFilterPredicate& argument_filter_predicate); - - // Flush all collected events to the given output callback. The callback will - // be called one or more times either synchronously or asynchronously from - // the current thread with IPC-bite-size chunks. The string format is - // undefined. Use TraceResultBuffer to convert one or more trace strings to - // JSON. The callback can be null if the caller doesn't want any data. - // Due to the implementation of thread-local buffers, flush can't be - // done when tracing is enabled. If called when tracing is enabled, the - // callback will be called directly with (empty_string, false) to indicate - // the end of this unsuccessful flush. Flush does the serialization - // on the same thread if the caller doesn't set use_worker_thread explicitly. - typedef base::Callback&, - bool has_more_events)> OutputCallback; - void Flush(const OutputCallback& cb, bool use_worker_thread = false); - - // Cancels tracing and discards collected data. - void CancelTracing(const OutputCallback& cb); - - // Called by TRACE_EVENT* macros, don't call this directly. - // The name parameter is a category group for example: - // TRACE_EVENT0("renderer,webkit", "WebViewImpl::HandleInputEvent") - static const unsigned char* GetCategoryGroupEnabled(const char* name); - static const char* GetCategoryGroupName( - const unsigned char* category_group_enabled); - - // Called by TRACE_EVENT* macros, don't call this directly. - // If |copy| is set, |name|, |arg_name1| and |arg_name2| will be deep copied - // into the event; see "Memory scoping note" and TRACE_EVENT_COPY_XXX above. - TraceEventHandle AddTraceEvent( - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags); - TraceEventHandle AddTraceEventWithBindId( - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - unsigned long long bind_id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags); - TraceEventHandle AddTraceEventWithProcessId( - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - int process_id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags); - TraceEventHandle AddTraceEventWithThreadIdAndTimestamp( - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - int thread_id, - const TimeTicks& timestamp, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags); - TraceEventHandle AddTraceEventWithThreadIdAndTimestamp( - char phase, - const unsigned char* category_group_enabled, - const char* name, - const char* scope, - unsigned long long id, - unsigned long long bind_id, - int thread_id, - const TimeTicks& timestamp, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags); - - // Adds a metadata event that will be written when the trace log is flushed. - void AddMetadataEvent( - const unsigned char* category_group_enabled, - const char* name, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - std::unique_ptr* convertable_values, - unsigned int flags); - - void UpdateTraceEventDuration(const unsigned char* category_group_enabled, - const char* name, - TraceEventHandle handle); - - void EndFilteredEvent(const unsigned char* category_group_enabled, - const char* name, - TraceEventHandle handle); - - int process_id() const { return process_id_; } - - uint64_t MangleEventId(uint64_t id); - - // Exposed for unittesting: - - // Testing factory for TraceEventFilter. - typedef std::unique_ptr (*FilterFactoryForTesting)( - const std::string& /* predicate_name */); - void SetFilterFactoryForTesting(FilterFactoryForTesting factory) { - filter_factory_for_testing_ = factory; - } - - // Allows deleting our singleton instance. - static void DeleteForTesting(); - - // Allow tests to inspect TraceEvents. - TraceEvent* GetEventByHandle(TraceEventHandle handle); - - void SetProcessID(int process_id); - - // Process sort indices, if set, override the order of a process will appear - // relative to other processes in the trace viewer. Processes are sorted first - // on their sort index, ascending, then by their name, and then tid. - void SetProcessSortIndex(int sort_index); - - // Sets the name of the process. |process_name| should be a string literal - // since it is a whitelisted argument for background field trials. - void SetProcessName(const char* process_name); - - // Processes can have labels in addition to their names. Use labels, for - // instance, to list out the web page titles that a process is handling. - void UpdateProcessLabel(int label_id, const std::string& current_label); - void RemoveProcessLabel(int label_id); - - // Thread sort indices, if set, override the order of a thread will appear - // within its process in the trace viewer. Threads are sorted first on their - // sort index, ascending, then by their name, and then tid. - void SetThreadSortIndex(PlatformThreadId thread_id, int sort_index); - - // Allow setting an offset between the current TimeTicks time and the time - // that should be reported. - void SetTimeOffset(TimeDelta offset); - - size_t GetObserverCountForTest() const; - - // Call this method if the current thread may block the message loop to - // prevent the thread from using the thread-local buffer because the thread - // may not handle the flush request in time causing lost of unflushed events. - void SetCurrentThreadBlocksMessageLoop(); - -#if defined(OS_WIN) - // This function is called by the ETW exporting module whenever the ETW - // keyword (flags) changes. This keyword indicates which categories should be - // exported, so whenever it changes, we adjust accordingly. - void UpdateETWCategoryGroupEnabledFlags(); -#endif - - private: - typedef unsigned int InternalTraceOptions; - - FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture, - TraceBufferRingBufferGetReturnChunk); - FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture, - TraceBufferRingBufferHalfIteration); - FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture, - TraceBufferRingBufferFullIteration); - FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture, TraceBufferVectorReportFull); - FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture, - ConvertTraceConfigToInternalOptions); - FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture, - TraceRecordAsMuchAsPossibleMode); - - // This allows constructor and destructor to be private and usable only - // by the Singleton class. - friend struct DefaultSingletonTraits; - - // MemoryDumpProvider implementation. - bool OnMemoryDump(const MemoryDumpArgs& args, - ProcessMemoryDump* pmd) override; - - // Enable/disable each category group based on the current mode_, - // category_filter_ and event_filters_enabled_. - // Enable the category group in the recording mode if category_filter_ matches - // the category group, is not null. Enable category for filtering if any - // filter in event_filters_enabled_ enables it. - void UpdateCategoryRegistry(); - void UpdateCategoryState(TraceCategory* category); - - void CreateFiltersForTraceConfig(); - - // Configure synthetic delays based on the values set in the current - // trace config. - void UpdateSyntheticDelaysFromTraceConfig(); - - InternalTraceOptions GetInternalOptionsFromTraceConfig( - const TraceConfig& config); - - class ThreadLocalEventBuffer; - class OptionalAutoLock; - struct RegisteredAsyncObserver; - - TraceLog(); - ~TraceLog() override; - void AddMetadataEventsWhileLocked(); - - InternalTraceOptions trace_options() const { - return static_cast( - subtle::NoBarrier_Load(&trace_options_)); - } - - TraceBuffer* trace_buffer() const { return logged_events_.get(); } - TraceBuffer* CreateTraceBuffer(); - - std::string EventToConsoleMessage(unsigned char phase, - const TimeTicks& timestamp, - TraceEvent* trace_event); - - TraceEvent* AddEventToThreadSharedChunkWhileLocked(TraceEventHandle* handle, - bool check_buffer_is_full); - void CheckIfBufferIsFullWhileLocked(); - void SetDisabledWhileLocked(uint8_t modes); - - TraceEvent* GetEventByHandleInternal(TraceEventHandle handle, - OptionalAutoLock* lock); - - void FlushInternal(const OutputCallback& cb, - bool use_worker_thread, - bool discard_events); - - // |generation| is used in the following callbacks to check if the callback - // is called for the flush of the current |logged_events_|. - void FlushCurrentThread(int generation, bool discard_events); - // Usually it runs on a different thread. - static void ConvertTraceEventsToTraceFormat( - std::unique_ptr logged_events, - const TraceLog::OutputCallback& flush_output_callback, - const ArgumentFilterPredicate& argument_filter_predicate); - void FinishFlush(int generation, bool discard_events); - void OnFlushTimeout(int generation, bool discard_events); - - int generation() const { - return static_cast(subtle::NoBarrier_Load(&generation_)); - } - bool CheckGeneration(int generation) const { - return generation == this->generation(); - } - void UseNextTraceBuffer(); - - TimeTicks OffsetNow() const { return OffsetTimestamp(TimeTicks::Now()); } - TimeTicks OffsetTimestamp(const TimeTicks& timestamp) const { - return timestamp - time_offset_; - } - - // Internal representation of trace options since we store the currently used - // trace option as an AtomicWord. - static const InternalTraceOptions kInternalNone; - static const InternalTraceOptions kInternalRecordUntilFull; - static const InternalTraceOptions kInternalRecordContinuously; - static const InternalTraceOptions kInternalEchoToConsole; - static const InternalTraceOptions kInternalRecordAsMuchAsPossible; - static const InternalTraceOptions kInternalEnableArgumentFilter; - - // This lock protects TraceLog member accesses (except for members protected - // by thread_info_lock_) from arbitrary threads. - mutable Lock lock_; - // This lock protects accesses to thread_names_, thread_event_start_times_ - // and thread_colors_. - Lock thread_info_lock_; - uint8_t enabled_modes_; // See TraceLog::Mode. - int num_traces_recorded_; - std::unique_ptr logged_events_; - std::vector> metadata_events_; - bool dispatching_to_observer_list_; - std::vector enabled_state_observer_list_; - std::map - async_observers_; - - std::string process_name_; - base::hash_map process_labels_; - int process_sort_index_; - base::hash_map thread_sort_indices_; - base::hash_map thread_names_; - - // The following two maps are used only when ECHO_TO_CONSOLE. - base::hash_map> thread_event_start_times_; - base::hash_map thread_colors_; - - TimeTicks buffer_limit_reached_timestamp_; - - // XORed with TraceID to make it unlikely to collide with other processes. - unsigned long long process_id_hash_; - - int process_id_; - - TimeDelta time_offset_; - - subtle::AtomicWord /* Options */ trace_options_; - - TraceConfig trace_config_; - TraceConfig::EventFilters enabled_event_filters_; - - ThreadLocalPointer thread_local_event_buffer_; - ThreadLocalBoolean thread_blocks_message_loop_; - ThreadLocalBoolean thread_is_in_trace_event_; - - // Contains the message loops of threads that have had at least one event - // added into the local event buffer. Not using SingleThreadTaskRunner - // because we need to know the life time of the message loops. - hash_set thread_message_loops_; - - // For events which can't be added into the thread local buffer, e.g. events - // from threads without a message loop. - std::unique_ptr thread_shared_chunk_; - size_t thread_shared_chunk_index_; - - // Set when asynchronous Flush is in progress. - OutputCallback flush_output_callback_; - scoped_refptr flush_task_runner_; - ArgumentFilterPredicate argument_filter_predicate_; - subtle::AtomicWord generation_; - bool use_worker_thread_; - - FilterFactoryForTesting filter_factory_for_testing_; - - DISALLOW_COPY_AND_ASSIGN(TraceLog); -}; - -} // namespace trace_event -} // namespace base - -#endif // BASE_TRACE_EVENT_TRACE_LOG_H_ diff --git a/base/trace_event/trace_log_constants.cc b/base/trace_event/trace_log_constants.cc deleted file mode 100644 index 65dca2e4d6f94807baa5ff19ae4ab6063caa82b0..0000000000000000000000000000000000000000 --- a/base/trace_event/trace_log_constants.cc +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2013 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. - -#include "base/trace_event/trace_log.h" - -namespace base { -namespace trace_event { - -// Constant used by TraceLog's internal implementation of trace_option. -const TraceLog::InternalTraceOptions - TraceLog::kInternalNone = 0; -const TraceLog::InternalTraceOptions - TraceLog::kInternalRecordUntilFull = 1 << 0; -const TraceLog::InternalTraceOptions - TraceLog::kInternalRecordContinuously = 1 << 1; -// 1 << 2 is reserved for the DEPRECATED kInternalEnableSampling. DO NOT USE. -const TraceLog::InternalTraceOptions - TraceLog::kInternalEchoToConsole = 1 << 3; -const TraceLog::InternalTraceOptions - TraceLog::kInternalRecordAsMuchAsPossible = 1 << 4; -const TraceLog::InternalTraceOptions - TraceLog::kInternalEnableArgumentFilter = 1 << 5; - -} // namespace trace_event -} // namespace base diff --git a/base/unguessable_token.cc b/base/unguessable_token.cc new file mode 100644 index 0000000000000000000000000000000000000000..cd9830e6866ff047350268107ecc18844a8b97c6 --- /dev/null +++ b/base/unguessable_token.cc @@ -0,0 +1,41 @@ +// Copyright 2016 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. + +#include "base/unguessable_token.h" + +#include "base/format_macros.h" +#include "base/rand_util.h" +#include "base/strings/stringprintf.h" + +namespace base { + +UnguessableToken::UnguessableToken(uint64_t high, uint64_t low) + : high_(high), low_(low) {} + +std::string UnguessableToken::ToString() const { + return base::StringPrintf("(%08" PRIX64 "%08" PRIX64 ")", high_, low_); +} + +// static +UnguessableToken UnguessableToken::Create() { + UnguessableToken token; + // Use base::RandBytes instead of crypto::RandBytes, because crypto calls the + // base version directly, and to prevent the dependency from base/ to crypto/. + base::RandBytes(&token, sizeof(token)); + return token; +} + +// static +UnguessableToken UnguessableToken::Deserialize(uint64_t high, uint64_t low) { + // Receiving a zeroed out UnguessableToken from another process means that it + // was never initialized via Create(). Treat this case as a security issue. + DCHECK(!(high == 0 && low == 0)); + return UnguessableToken(high, low); +} + +std::ostream& operator<<(std::ostream& out, const UnguessableToken& token) { + return out << token.ToString(); +} + +} // namespace base diff --git a/base/unguessable_token.h b/base/unguessable_token.h new file mode 100644 index 0000000000000000000000000000000000000000..9f38783a3ca8c482f7072c909f249f0cc752609d --- /dev/null +++ b/base/unguessable_token.h @@ -0,0 +1,103 @@ +// Copyright 2016 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 BASE_UNGUESSABLE_TOKEN_H_ +#define BASE_UNGUESSABLE_TOKEN_H_ + +#include +#include +#include +#include + +#include "base/base_export.h" +#include "base/hash.h" +#include "base/logging.h" + +namespace base { + +struct UnguessableTokenHash; + +// A UnguessableToken is an 128-bit token generated from a cryptographically +// strong random source. +// +// UnguessableToken should be used when a sensitive ID needs to be unguessable, +// and is shared across processes. It can be used as part of a larger aggregate +// type, or as an ID in and of itself. +// +// Use Create() for creating new UnguessableTokens. +// +// NOTE: It is illegal to send empty UnguessableTokens across processes, and +// sending/receiving empty tokens should be treated as a security issue. +// If there is a valid scenario for sending "no token" across processes, +// base::Optional should be used instead of an empty token. +class BASE_EXPORT UnguessableToken { + public: + // Create a unique UnguessableToken. + static UnguessableToken Create(); + + // Return a UnguessableToken built from the high/low bytes provided. + // It should only be used in deserialization scenarios. + // + // NOTE: If the deserialized token is empty, it means that it was never + // initialized via Create(). This is a security issue, and should be handled. + static UnguessableToken Deserialize(uint64_t high, uint64_t low); + + // Creates an empty UnguessableToken. + // Assign to it with Create() before using it. + constexpr UnguessableToken() = default; + + // NOTE: Serializing an empty UnguessableToken is an illegal operation. + uint64_t GetHighForSerialization() const { + DCHECK(!is_empty()); + return high_; + }; + + // NOTE: Serializing an empty UnguessableToken is an illegal operation. + uint64_t GetLowForSerialization() const { + DCHECK(!is_empty()); + return low_; + } + + bool is_empty() const { return high_ == 0 && low_ == 0; } + + std::string ToString() const; + + explicit operator bool() const { return !is_empty(); } + + bool operator<(const UnguessableToken& other) const { + return std::tie(high_, low_) < std::tie(other.high_, other.low_); + } + + bool operator==(const UnguessableToken& other) const { + return high_ == other.high_ && low_ == other.low_; + } + + bool operator!=(const UnguessableToken& other) const { + return !(*this == other); + } + + private: + friend struct UnguessableTokenHash; + UnguessableToken(uint64_t high, uint64_t low); + + // Note: Two uint64_t are used instead of uint8_t[16], in order to have a + // simpler ToString() and is_empty(). + uint64_t high_ = 0; + uint64_t low_ = 0; +}; + +BASE_EXPORT std::ostream& operator<<(std::ostream& out, + const UnguessableToken& token); + +// For use in std::unordered_map. +struct UnguessableTokenHash { + size_t operator()(const base::UnguessableToken& token) const { + DCHECK(token); + return base::HashInts64(token.high_, token.low_); + } +}; + +} // namespace base + +#endif // BASE_UNGUESSABLE_TOKEN_H_ diff --git a/build/android/gyp/util/__init__.py b/build/android/gyp/util/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..727e987e6b621957ed56f20af1968525a9abadcf --- /dev/null +++ b/build/android/gyp/util/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2012 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. + diff --git a/build/android/gyp/util/build_utils.py b/build/android/gyp/util/build_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..abd2dfc80f81ddb838e447ed70a73b581b39b637 --- /dev/null +++ b/build/android/gyp/util/build_utils.py @@ -0,0 +1,589 @@ +# Copyright 2013 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 ast +import contextlib +import fnmatch +import json +import os +import pipes +import re +import shlex +import shutil +import stat +import subprocess +import sys +import tempfile +import zipfile + +# Some clients do not add //build/android/gyp to PYTHONPATH. +import md5_check # pylint: disable=relative-import + +# pylib conflicts with mojo/public/tools/bindings/pylib. Prioritize +# build/android/pylib. +# PYTHONPATH wouldn't help in this case, because soong put source files under +# temp directory for each build, so the abspath is unknown until the +# execution. +# sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +from pylib.constants import host_paths + +sys.path.append(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, os.pardir)) +import gn_helpers + +COLORAMA_ROOT = os.path.join(host_paths.DIR_SOURCE_ROOT, + 'third_party', 'colorama', 'src') +# aapt should ignore OWNERS files in addition the default ignore pattern. +AAPT_IGNORE_PATTERN = ('!OWNERS:!.svn:!.git:!.ds_store:!*.scc:.*:

_*:' + + '!CVS:!thumbs.db:!picasa.ini:!*~:!*.d.stamp') +HERMETIC_TIMESTAMP = (2001, 1, 1, 0, 0, 0) +_HERMETIC_FILE_ATTR = (0644 << 16L) + + +@contextlib.contextmanager +def TempDir(): + dirname = tempfile.mkdtemp() + try: + yield dirname + finally: + shutil.rmtree(dirname) + + +def MakeDirectory(dir_path): + try: + os.makedirs(dir_path) + except OSError: + pass + + +def DeleteDirectory(dir_path): + if os.path.exists(dir_path): + shutil.rmtree(dir_path) + + +def Touch(path, fail_if_missing=False): + if fail_if_missing and not os.path.exists(path): + raise Exception(path + ' doesn\'t exist.') + + MakeDirectory(os.path.dirname(path)) + with open(path, 'a'): + os.utime(path, None) + + +def FindInDirectory(directory, filename_filter): + files = [] + for root, _dirnames, filenames in os.walk(directory): + matched_files = fnmatch.filter(filenames, filename_filter) + files.extend((os.path.join(root, f) for f in matched_files)) + return files + + +def FindInDirectories(directories, filename_filter): + all_files = [] + for directory in directories: + all_files.extend(FindInDirectory(directory, filename_filter)) + return all_files + + +def ParseGnList(gn_string): + """Converts a command-line parameter into a list. + + If the input starts with a '[' it is assumed to be a GN-formatted list and + it will be parsed accordingly. When empty an empty list will be returned. + Otherwise, the parameter will be treated as a single raw string (not + GN-formatted in that it's not assumed to have literal quotes that must be + removed) and a list will be returned containing that string. + + The common use for this behavior is in the Android build where things can + take lists of @FileArg references that are expanded via ExpandFileArgs. + """ + if gn_string.startswith('['): + parser = gn_helpers.GNValueParser(gn_string) + return parser.ParseList() + if len(gn_string): + return [ gn_string ] + return [] + + +def CheckOptions(options, parser, required=None): + if not required: + return + for option_name in required: + if getattr(options, option_name) is None: + parser.error('--%s is required' % option_name.replace('_', '-')) + + +def WriteJson(obj, path, only_if_changed=False): + old_dump = None + if os.path.exists(path): + with open(path, 'r') as oldfile: + old_dump = oldfile.read() + + new_dump = json.dumps(obj, sort_keys=True, indent=2, separators=(',', ': ')) + + if not only_if_changed or old_dump != new_dump: + with open(path, 'w') as outfile: + outfile.write(new_dump) + + +def ReadJson(path): + with open(path, 'r') as jsonfile: + return json.load(jsonfile) + + +class CalledProcessError(Exception): + """This exception is raised when the process run by CheckOutput + exits with a non-zero exit code.""" + + def __init__(self, cwd, args, output): + super(CalledProcessError, self).__init__() + self.cwd = cwd + self.args = args + self.output = output + + def __str__(self): + # A user should be able to simply copy and paste the command that failed + # into their shell. + copyable_command = '( cd {}; {} )'.format(os.path.abspath(self.cwd), + ' '.join(map(pipes.quote, self.args))) + return 'Command failed: {}\n{}'.format(copyable_command, self.output) + + +# This can be used in most cases like subprocess.check_output(). The output, +# particularly when the command fails, better highlights the command's failure. +# If the command fails, raises a build_utils.CalledProcessError. +def CheckOutput(args, cwd=None, env=None, + print_stdout=False, print_stderr=True, + stdout_filter=None, + stderr_filter=None, + fail_func=lambda returncode, stderr: returncode != 0): + if not cwd: + cwd = os.getcwd() + + child = subprocess.Popen(args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env) + stdout, stderr = child.communicate() + + if stdout_filter is not None: + stdout = stdout_filter(stdout) + + if stderr_filter is not None: + stderr = stderr_filter(stderr) + + if fail_func(child.returncode, stderr): + raise CalledProcessError(cwd, args, stdout + stderr) + + if print_stdout: + sys.stdout.write(stdout) + if print_stderr: + sys.stderr.write(stderr) + + return stdout + + +def GetModifiedTime(path): + # For a symlink, the modified time should be the greater of the link's + # modified time and the modified time of the target. + return max(os.lstat(path).st_mtime, os.stat(path).st_mtime) + + +def IsTimeStale(output, inputs): + if not os.path.exists(output): + return True + + output_time = GetModifiedTime(output) + for i in inputs: + if GetModifiedTime(i) > output_time: + return True + return False + + +def IsDeviceReady(): + device_state = CheckOutput(['adb', 'get-state']) + return device_state.strip() == 'device' + + +def CheckZipPath(name): + if os.path.normpath(name) != name: + raise Exception('Non-canonical zip path: %s' % name) + if os.path.isabs(name): + raise Exception('Absolute zip path: %s' % name) + + +def IsSymlink(zip_file, name): + zi = zip_file.getinfo(name) + + # The two high-order bytes of ZipInfo.external_attr represent + # UNIX permissions and file type bits. + return stat.S_ISLNK(zi.external_attr >> 16L) + + +def ExtractAll(zip_path, path=None, no_clobber=True, pattern=None, + predicate=None): + if path is None: + path = os.getcwd() + elif not os.path.exists(path): + MakeDirectory(path) + + if not zipfile.is_zipfile(zip_path): + raise Exception('Invalid zip file: %s' % zip_path) + + extracted = [] + with zipfile.ZipFile(zip_path) as z: + for name in z.namelist(): + if name.endswith('/'): + continue + if pattern is not None: + if not fnmatch.fnmatch(name, pattern): + continue + if predicate and not predicate(name): + continue + CheckZipPath(name) + if no_clobber: + output_path = os.path.join(path, name) + if os.path.exists(output_path): + raise Exception( + 'Path already exists from zip: %s %s %s' + % (zip_path, name, output_path)) + if IsSymlink(z, name): + dest = os.path.join(path, name) + MakeDirectory(os.path.dirname(dest)) + os.symlink(z.read(name), dest) + extracted.append(dest) + else: + z.extract(name, path) + extracted.append(os.path.join(path, name)) + + return extracted + + +def AddToZipHermetic(zip_file, zip_path, src_path=None, data=None, + compress=None): + """Adds a file to the given ZipFile with a hard-coded modified time. + + Args: + zip_file: ZipFile instance to add the file to. + zip_path: Destination path within the zip file. + src_path: Path of the source file. Mutually exclusive with |data|. + data: File data as a string. + compress: Whether to enable compression. Default is take from ZipFile + constructor. + """ + assert (src_path is None) != (data is None), ( + '|src_path| and |data| are mutually exclusive.') + CheckZipPath(zip_path) + zipinfo = zipfile.ZipInfo(filename=zip_path, date_time=HERMETIC_TIMESTAMP) + zipinfo.external_attr = _HERMETIC_FILE_ATTR + + if src_path and os.path.islink(src_path): + zipinfo.filename = zip_path + zipinfo.external_attr |= stat.S_IFLNK << 16L # mark as a symlink + zip_file.writestr(zipinfo, os.readlink(src_path)) + return + + if src_path: + with file(src_path) as f: + data = f.read() + + # zipfile will deflate even when it makes the file bigger. To avoid + # growing files, disable compression at an arbitrary cut off point. + if len(data) < 16: + compress = False + + # None converts to ZIP_STORED, when passed explicitly rather than the + # default passed to the ZipFile constructor. + compress_type = zip_file.compression + if compress is not None: + compress_type = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED + zip_file.writestr(zipinfo, data, compress_type) + + +def DoZip(inputs, output, base_dir=None): + """Creates a zip file from a list of files. + + Args: + inputs: A list of paths to zip, or a list of (zip_path, fs_path) tuples. + output: Destination .zip file. + base_dir: Prefix to strip from inputs. + """ + input_tuples = [] + for tup in inputs: + if isinstance(tup, basestring): + tup = (os.path.relpath(tup, base_dir), tup) + input_tuples.append(tup) + + # Sort by zip path to ensure stable zip ordering. + input_tuples.sort(key=lambda tup: tup[0]) + with zipfile.ZipFile(output, 'w') as outfile: + for zip_path, fs_path in input_tuples: + AddToZipHermetic(outfile, zip_path, src_path=fs_path) + + +def ZipDir(output, base_dir): + """Creates a zip file from a directory.""" + inputs = [] + for root, _, files in os.walk(base_dir): + for f in files: + inputs.append(os.path.join(root, f)) + DoZip(inputs, output, base_dir) + + +def MatchesGlob(path, filters): + """Returns whether the given path matches any of the given glob patterns.""" + return filters and any(fnmatch.fnmatch(path, f) for f in filters) + + +def MergeZips(output, inputs, exclude_patterns=None, path_transform=None): + path_transform = path_transform or (lambda p, z: p) + added_names = set() + + output_is_already_open = not isinstance(output, basestring) + if output_is_already_open: + assert isinstance(output, zipfile.ZipFile) + out_zip = output + else: + out_zip = zipfile.ZipFile(output, 'w') + + try: + for in_file in inputs: + with zipfile.ZipFile(in_file, 'r') as in_zip: + in_zip._expected_crc = None + for info in in_zip.infolist(): + # Ignore directories. + if info.filename[-1] == '/': + continue + dst_name = path_transform(info.filename, in_file) + already_added = dst_name in added_names + if not already_added and not MatchesGlob(dst_name, exclude_patterns): + AddToZipHermetic(out_zip, dst_name, data=in_zip.read(info), + compress=info.compress_type != zipfile.ZIP_STORED) + added_names.add(dst_name) + finally: + if not output_is_already_open: + out_zip.close() + + +def PrintWarning(message): + print 'WARNING: ' + message + + +def PrintBigWarning(message): + print '***** ' * 8 + PrintWarning(message) + print '***** ' * 8 + + +def GetSortedTransitiveDependencies(top, deps_func): + """Gets the list of all transitive dependencies in sorted order. + + There should be no cycles in the dependency graph. + + Args: + top: a list of the top level nodes + deps_func: A function that takes a node and returns its direct dependencies. + Returns: + A list of all transitive dependencies of nodes in top, in order (a node will + appear in the list at a higher index than all of its dependencies). + """ + def Node(dep): + return (dep, deps_func(dep)) + + # First: find all deps + unchecked_deps = list(top) + all_deps = set(top) + while unchecked_deps: + dep = unchecked_deps.pop() + new_deps = deps_func(dep).difference(all_deps) + unchecked_deps.extend(new_deps) + all_deps = all_deps.union(new_deps) + + # Then: simple, slow topological sort. + sorted_deps = [] + unsorted_deps = dict(map(Node, all_deps)) + while unsorted_deps: + for library, dependencies in unsorted_deps.items(): + if not dependencies.intersection(unsorted_deps.keys()): + sorted_deps.append(library) + del unsorted_deps[library] + + return sorted_deps + + +def GetPythonDependencies(): + """Gets the paths of imported non-system python modules. + + A path is assumed to be a "system" import if it is outside of chromium's + src/. The paths will be relative to the current directory. + """ + module_paths = GetModulePaths() + + abs_module_paths = map(os.path.abspath, module_paths) + + assert os.path.isabs(host_paths.DIR_SOURCE_ROOT) + non_system_module_paths = [ + p for p in abs_module_paths if p.startswith(host_paths.DIR_SOURCE_ROOT)] + def ConvertPycToPy(s): + if s.endswith('.pyc'): + return s[:-1] + return s + + non_system_module_paths = map(ConvertPycToPy, non_system_module_paths) + non_system_module_paths = map(os.path.relpath, non_system_module_paths) + return sorted(set(non_system_module_paths)) + + +def GetModulePaths(): + """Returns the paths to all of the modules in sys.modules.""" + ForceLazyModulesToLoad() + return (m.__file__ for m in sys.modules.itervalues() + if m is not None and hasattr(m, '__file__')) + + +def ForceLazyModulesToLoad(): + """Forces any lazily imported modules to fully load themselves. + + Inspecting the modules' __file__ attribute causes lazily imported modules + (e.g. from email) to get fully imported and update sys.modules. Iterate + over the values until sys.modules stabilizes so that no modules are missed. + """ + while True: + num_modules_before = len(sys.modules.keys()) + for m in sys.modules.values(): + if m is not None and hasattr(m, '__file__'): + _ = m.__file__ + num_modules_after = len(sys.modules.keys()) + if num_modules_before == num_modules_after: + break + + +def AddDepfileOption(parser): + # TODO(agrieve): Get rid of this once we've moved to argparse. + if hasattr(parser, 'add_option'): + func = parser.add_option + else: + func = parser.add_argument + func('--depfile', + help='Path to depfile (refer to `gn help depfile`)') + + +def WriteDepfile(depfile_path, first_gn_output, inputs=None, add_pydeps=True): + assert depfile_path != first_gn_output # http://crbug.com/646165 + inputs = inputs or [] + if add_pydeps: + inputs = GetPythonDependencies() + inputs + MakeDirectory(os.path.dirname(depfile_path)) + # Ninja does not support multiple outputs in depfiles. + with open(depfile_path, 'w') as depfile: + depfile.write(first_gn_output.replace(' ', '\\ ')) + depfile.write(': ') + depfile.write(' '.join(i.replace(' ', '\\ ') for i in inputs)) + depfile.write('\n') + + +def ExpandFileArgs(args): + """Replaces file-arg placeholders in args. + + These placeholders have the form: + @FileArg(filename:key1:key2:...:keyn) + + The value of such a placeholder is calculated by reading 'filename' as json. + And then extracting the value at [key1][key2]...[keyn]. + + Note: This intentionally does not return the list of files that appear in such + placeholders. An action that uses file-args *must* know the paths of those + files prior to the parsing of the arguments (typically by explicitly listing + them in the action's inputs in build files). + """ + new_args = list(args) + file_jsons = dict() + r = re.compile('@FileArg\((.*?)\)') + for i, arg in enumerate(args): + match = r.search(arg) + if not match: + continue + + if match.end() != len(arg): + raise Exception('Unexpected characters after FileArg: ' + arg) + + lookup_path = match.group(1).split(':') + file_path = lookup_path[0] + if not file_path in file_jsons: + file_jsons[file_path] = ReadJson(file_path) + + expansion = file_jsons[file_path] + for k in lookup_path[1:]: + expansion = expansion[k] + + # This should match ParseGNList. The output is either a GN-formatted list + # or a literal (with no quotes). + if isinstance(expansion, list): + new_args[i] = arg[:match.start()] + gn_helpers.ToGNString(expansion) + else: + new_args[i] = arg[:match.start()] + str(expansion) + + return new_args + + +def ReadSourcesList(sources_list_file_name): + """Reads a GN-written file containing list of file names and returns a list. + + Note that this function should not be used to parse response files. + """ + with open(sources_list_file_name) as f: + return [file_name.strip() for file_name in f] + + +def CallAndWriteDepfileIfStale(function, options, record_path=None, + input_paths=None, input_strings=None, + output_paths=None, force=False, + pass_changes=False, + depfile_deps=None): + """Wraps md5_check.CallAndRecordIfStale() and also writes dep & stamp files. + + Depfiles and stamp files are automatically added to output_paths when present + in the |options| argument. They are then created after |function| is called. + + By default, only python dependencies are added to the depfile. If there are + other input paths that are not captured by GN deps, then they should be listed + in depfile_deps. It's important to write paths to the depfile that are already + captured by GN deps since GN args can cause GN deps to change, and such + changes are not immediately reflected in depfiles (http://crbug.com/589311). + """ + if not output_paths: + raise Exception('At least one output_path must be specified.') + input_paths = list(input_paths or []) + input_strings = list(input_strings or []) + output_paths = list(output_paths or []) + + python_deps = None + if hasattr(options, 'depfile') and options.depfile: + python_deps = GetPythonDependencies() + input_paths += python_deps + output_paths += [options.depfile] + + stamp_file = hasattr(options, 'stamp') and options.stamp + if stamp_file: + output_paths += [stamp_file] + + def on_stale_md5(changes): + args = (changes,) if pass_changes else () + function(*args) + if python_deps is not None: + all_depfile_deps = list(python_deps) + if depfile_deps: + all_depfile_deps.extend(depfile_deps) + WriteDepfile(options.depfile, output_paths[0], all_depfile_deps, + add_pydeps=False) + if stamp_file: + Touch(stamp_file) + + md5_check.CallAndRecordIfStale( + on_stale_md5, + record_path=record_path, + input_paths=input_paths, + input_strings=input_strings, + output_paths=output_paths, + force=force, + pass_changes=True) diff --git a/build/android/gyp/util/md5_check.py b/build/android/gyp/util/md5_check.py new file mode 100644 index 0000000000000000000000000000000000000000..76591249bac14ee4e4d3a4d0725f62f019c89379 --- /dev/null +++ b/build/android/gyp/util/md5_check.py @@ -0,0 +1,410 @@ +# Copyright 2013 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 difflib +import hashlib +import itertools +import json +import os +import sys +import zipfile + + +# When set and a difference is detected, a diff of what changed is printed. +PRINT_EXPLANATIONS = int(os.environ.get('PRINT_BUILD_EXPLANATIONS', 0)) + +# An escape hatch that causes all targets to be rebuilt. +_FORCE_REBUILD = int(os.environ.get('FORCE_REBUILD', 0)) + + +def CallAndRecordIfStale( + function, record_path=None, input_paths=None, input_strings=None, + output_paths=None, force=False, pass_changes=False): + """Calls function if outputs are stale. + + Outputs are considered stale if: + - any output_paths are missing, or + - the contents of any file within input_paths has changed, or + - the contents of input_strings has changed. + + To debug which files are out-of-date, set the environment variable: + PRINT_MD5_DIFFS=1 + + Args: + function: The function to call. + record_path: Path to record metadata. + Defaults to output_paths[0] + '.md5.stamp' + input_paths: List of paths to calcualte an md5 sum on. + input_strings: List of strings to record verbatim. + output_paths: List of output paths. + force: Whether to treat outputs as missing regardless of whether they + actually are. + pass_changes: Whether to pass a Changes instance to |function|. + """ + assert record_path or output_paths + input_paths = input_paths or [] + input_strings = input_strings or [] + output_paths = output_paths or [] + record_path = record_path or output_paths[0] + '.md5.stamp' + + assert record_path.endswith('.stamp'), ( + 'record paths must end in \'.stamp\' so that they are easy to find ' + 'and delete') + + new_metadata = _Metadata() + new_metadata.AddStrings(input_strings) + + for path in input_paths: + if _IsZipFile(path): + entries = _ExtractZipEntries(path) + new_metadata.AddZipFile(path, entries) + else: + new_metadata.AddFile(path, _Md5ForPath(path)) + + old_metadata = None + force = force or _FORCE_REBUILD + missing_outputs = [x for x in output_paths if force or not os.path.exists(x)] + # When outputs are missing, don't bother gathering change information. + if not missing_outputs and os.path.exists(record_path): + with open(record_path, 'r') as jsonfile: + try: + old_metadata = _Metadata.FromFile(jsonfile) + except: # pylint: disable=bare-except + pass # Not yet using new file format. + + changes = Changes(old_metadata, new_metadata, force, missing_outputs) + if not changes.HasChanges(): + return + + if PRINT_EXPLANATIONS: + print '=' * 80 + print 'Target is stale: %s' % record_path + print changes.DescribeDifference() + print '=' * 80 + + args = (changes,) if pass_changes else () + function(*args) + + with open(record_path, 'w') as f: + new_metadata.ToFile(f) + + +class Changes(object): + """Provides and API for querying what changed between runs.""" + + def __init__(self, old_metadata, new_metadata, force, missing_outputs): + self.old_metadata = old_metadata + self.new_metadata = new_metadata + self.force = force + self.missing_outputs = missing_outputs + + def _GetOldTag(self, path, subpath=None): + return self.old_metadata and self.old_metadata.GetTag(path, subpath) + + def HasChanges(self): + """Returns whether any changes exist.""" + return (self.force or + not self.old_metadata or + self.old_metadata.StringsMd5() != self.new_metadata.StringsMd5() or + self.old_metadata.FilesMd5() != self.new_metadata.FilesMd5()) + + def AddedOrModifiedOnly(self): + """Returns whether the only changes were from added or modified (sub)files. + + No missing outputs, no removed paths/subpaths. + """ + if (self.force or + not self.old_metadata or + self.old_metadata.StringsMd5() != self.new_metadata.StringsMd5()): + return False + if any(self.IterRemovedPaths()): + return False + for path in self.IterModifiedPaths(): + if any(self.IterRemovedSubpaths(path)): + return False + return True + + def IterAllPaths(self): + """Generator for paths.""" + return self.new_metadata.IterPaths(); + + def IterAllSubpaths(self, path): + """Generator for subpaths.""" + return self.new_metadata.IterSubpaths(path); + + def IterAddedPaths(self): + """Generator for paths that were added.""" + for path in self.new_metadata.IterPaths(): + if self._GetOldTag(path) is None: + yield path + + def IterAddedSubpaths(self, path): + """Generator for paths that were added within the given zip file.""" + for subpath in self.new_metadata.IterSubpaths(path): + if self._GetOldTag(path, subpath) is None: + yield subpath + + def IterRemovedPaths(self): + """Generator for paths that were removed.""" + if self.old_metadata: + for path in self.old_metadata.IterPaths(): + if self.new_metadata.GetTag(path) is None: + yield path + + def IterRemovedSubpaths(self, path): + """Generator for paths that were removed within the given zip file.""" + if self.old_metadata: + for subpath in self.old_metadata.IterSubpaths(path): + if self.new_metadata.GetTag(path, subpath) is None: + yield subpath + + def IterModifiedPaths(self): + """Generator for paths whose contents have changed.""" + for path in self.new_metadata.IterPaths(): + old_tag = self._GetOldTag(path) + new_tag = self.new_metadata.GetTag(path) + if old_tag is not None and old_tag != new_tag: + yield path + + def IterModifiedSubpaths(self, path): + """Generator for paths within a zip file whose contents have changed.""" + for subpath in self.new_metadata.IterSubpaths(path): + old_tag = self._GetOldTag(path, subpath) + new_tag = self.new_metadata.GetTag(path, subpath) + if old_tag is not None and old_tag != new_tag: + yield subpath + + def IterChangedPaths(self): + """Generator for all changed paths (added/removed/modified).""" + return itertools.chain(self.IterRemovedPaths(), + self.IterModifiedPaths(), + self.IterAddedPaths()) + + def IterChangedSubpaths(self, path): + """Generator for paths within a zip that were added/removed/modified.""" + return itertools.chain(self.IterRemovedSubpaths(path), + self.IterModifiedSubpaths(path), + self.IterAddedSubpaths(path)) + + def DescribeDifference(self): + """Returns a human-readable description of what changed.""" + if self.force: + return 'force=True' + elif self.missing_outputs: + return 'Outputs do not exist:\n ' + '\n '.join(self.missing_outputs) + elif self.old_metadata is None: + return 'Previous stamp file not found.' + + if self.old_metadata.StringsMd5() != self.new_metadata.StringsMd5(): + ndiff = difflib.ndiff(self.old_metadata.GetStrings(), + self.new_metadata.GetStrings()) + changed = [s for s in ndiff if not s.startswith(' ')] + return 'Input strings changed:\n ' + '\n '.join(changed) + + if self.old_metadata.FilesMd5() == self.new_metadata.FilesMd5(): + return "There's no difference." + + lines = [] + lines.extend('Added: ' + p for p in self.IterAddedPaths()) + lines.extend('Removed: ' + p for p in self.IterRemovedPaths()) + for path in self.IterModifiedPaths(): + lines.append('Modified: ' + path) + lines.extend(' -> Subpath added: ' + p + for p in self.IterAddedSubpaths(path)) + lines.extend(' -> Subpath removed: ' + p + for p in self.IterRemovedSubpaths(path)) + lines.extend(' -> Subpath modified: ' + p + for p in self.IterModifiedSubpaths(path)) + if lines: + return 'Input files changed:\n ' + '\n '.join(lines) + return 'I have no idea what changed (there is a bug).' + + +class _Metadata(object): + """Data model for tracking change metadata.""" + # Schema: + # { + # "files-md5": "VALUE", + # "strings-md5": "VALUE", + # "input-files": [ + # { + # "path": "path.jar", + # "tag": "{MD5 of entries}", + # "entries": [ + # { "path": "org/chromium/base/Foo.class", "tag": "{CRC32}" }, ... + # ] + # }, { + # "path": "path.txt", + # "tag": "{MD5}", + # } + # ], + # "input-strings": ["a", "b", ...], + # } + def __init__(self): + self._files_md5 = None + self._strings_md5 = None + self._files = [] + self._strings = [] + # Map of (path, subpath) -> entry. Created upon first call to _GetEntry(). + self._file_map = None + + @classmethod + def FromFile(cls, fileobj): + """Returns a _Metadata initialized from a file object.""" + ret = cls() + obj = json.load(fileobj) + ret._files_md5 = obj['files-md5'] + ret._strings_md5 = obj['strings-md5'] + ret._files = obj['input-files'] + ret._strings = obj['input-strings'] + return ret + + def ToFile(self, fileobj): + """Serializes metadata to the given file object.""" + obj = { + "files-md5": self.FilesMd5(), + "strings-md5": self.StringsMd5(), + "input-files": self._files, + "input-strings": self._strings, + } + json.dump(obj, fileobj, indent=2) + + def _AssertNotQueried(self): + assert self._files_md5 is None + assert self._strings_md5 is None + assert self._file_map is None + + def AddStrings(self, values): + self._AssertNotQueried() + self._strings.extend(str(v) for v in values) + + def AddFile(self, path, tag): + """Adds metadata for a non-zip file. + + Args: + path: Path to the file. + tag: A short string representative of the file contents. + """ + self._AssertNotQueried() + self._files.append({ + 'path': path, + 'tag': tag, + }) + + def AddZipFile(self, path, entries): + """Adds metadata for a zip file. + + Args: + path: Path to the file. + entries: List of (subpath, tag) tuples for entries within the zip. + """ + self._AssertNotQueried() + tag = _ComputeInlineMd5(itertools.chain((e[0] for e in entries), + (e[1] for e in entries))) + self._files.append({ + 'path': path, + 'tag': tag, + 'entries': [{"path": e[0], "tag": e[1]} for e in entries], + }) + + def GetStrings(self): + """Returns the list of input strings.""" + return self._strings + + def FilesMd5(self): + """Lazily computes and returns the aggregate md5 of input files.""" + if self._files_md5 is None: + # Omit paths from md5 since temporary files have random names. + self._files_md5 = _ComputeInlineMd5( + self.GetTag(p) for p in sorted(self.IterPaths())) + return self._files_md5 + + def StringsMd5(self): + """Lazily computes and returns the aggregate md5 of input strings.""" + if self._strings_md5 is None: + self._strings_md5 = _ComputeInlineMd5(self._strings) + return self._strings_md5 + + def _GetEntry(self, path, subpath=None): + """Returns the JSON entry for the given path / subpath.""" + if self._file_map is None: + self._file_map = {} + for entry in self._files: + self._file_map[(entry['path'], None)] = entry + for subentry in entry.get('entries', ()): + self._file_map[(entry['path'], subentry['path'])] = subentry + return self._file_map.get((path, subpath)) + + def GetTag(self, path, subpath=None): + """Returns the tag for the given path / subpath.""" + ret = self._GetEntry(path, subpath) + return ret and ret['tag'] + + def IterPaths(self): + """Returns a generator for all top-level paths.""" + return (e['path'] for e in self._files) + + def IterSubpaths(self, path): + """Returns a generator for all subpaths in the given zip. + + If the given path is not a zip file or doesn't exist, returns an empty + iterable. + """ + outer_entry = self._GetEntry(path) + if not outer_entry: + return () + subentries = outer_entry.get('entries', []) + return (entry['path'] for entry in subentries) + + +def _UpdateMd5ForFile(md5, path, block_size=2**16): + with open(path, 'rb') as infile: + while True: + data = infile.read(block_size) + if not data: + break + md5.update(data) + + +def _UpdateMd5ForDirectory(md5, dir_path): + for root, _, files in os.walk(dir_path): + for f in files: + _UpdateMd5ForFile(md5, os.path.join(root, f)) + + +def _Md5ForPath(path): + md5 = hashlib.md5() + if os.path.isdir(path): + _UpdateMd5ForDirectory(md5, path) + else: + _UpdateMd5ForFile(md5, path) + return md5.hexdigest() + + +def _ComputeInlineMd5(iterable): + """Computes the md5 of the concatenated parameters.""" + md5 = hashlib.md5() + for item in iterable: + md5.update(str(item)) + return md5.hexdigest() + + +def _IsZipFile(path): + """Returns whether to treat the given file as a zip file.""" + # ijar doesn't set the CRC32 field. + if path.endswith('.interface.jar'): + return False + return path[-4:] in ('.zip', '.apk', '.jar') or path.endswith('.srcjar') + + +def _ExtractZipEntries(path): + """Returns a list of (path, CRC32) of all files within |path|.""" + entries = [] + with zipfile.ZipFile(path) as zip_file: + for zip_info in zip_file.infolist(): + # Skip directories and empty files. + if zip_info.CRC: + entries.append( + (zip_info.filename, zip_info.CRC + zip_info.compress_type)) + return entries diff --git a/build/android/pylib/__init__.py b/build/android/pylib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b93eb4fe0be1f46a8bdf1cb708fbe0d24ed7c91a --- /dev/null +++ b/build/android/pylib/__init__.py @@ -0,0 +1,31 @@ +# Copyright (c) 2012 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 os +import sys + + +_CATAPULT_PATH = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', '..', 'third_party', 'catapult')) + +_DEVIL_PATH = os.path.join(_CATAPULT_PATH, 'devil') + +_PYTRACE_PATH = os.path.join(_CATAPULT_PATH, 'common', 'py_trace_event') + +_PY_UTILS_PATH = os.path.join(_CATAPULT_PATH, 'common', 'py_utils') + +_TRACE2HTML_PATH = os.path.join(_CATAPULT_PATH, 'tracing') + + +if _DEVIL_PATH not in sys.path: + sys.path.append(_DEVIL_PATH) + +if _PYTRACE_PATH not in sys.path: + sys.path.append(_PYTRACE_PATH) + +if _PY_UTILS_PATH not in sys.path: + sys.path.append(_PY_UTILS_PATH) + +if _TRACE2HTML_PATH not in sys.path: + sys.path.append(_TRACE2HTML_PATH) diff --git a/build/android/pylib/constants/__init__.py b/build/android/pylib/constants/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..916ee275564a6664b03250864ff9639e2303cdb3 --- /dev/null +++ b/build/android/pylib/constants/__init__.py @@ -0,0 +1,225 @@ +# Copyright (c) 2012 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. + +"""Defines a set of constants shared by test runners and other scripts.""" + +# TODO(jbudorick): Split these constants into coherent modules. + +# pylint: disable=W0212 + +import collections +import glob +import logging +import os +import subprocess + +import devil.android.sdk.keyevent +from devil.android.constants import chrome +from devil.android.sdk import version_codes +from devil.constants import exit_codes + + +keyevent = devil.android.sdk.keyevent + + +DIR_SOURCE_ROOT = os.environ.get('CHECKOUT_SOURCE_ROOT', + os.path.abspath(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, os.pardir, os.pardir))) + +PACKAGE_INFO = dict(chrome.PACKAGE_INFO) +PACKAGE_INFO.update({ + 'legacy_browser': chrome.PackageInfo( + 'com.google.android.browser', + 'com.android.browser.BrowserActivity', + None, + None), + 'chromecast_shell': chrome.PackageInfo( + 'com.google.android.apps.mediashell', + 'com.google.android.apps.mediashell.MediaShellActivity', + 'castshell-command-line', + None), + 'android_webview_shell': chrome.PackageInfo( + 'org.chromium.android_webview.shell', + 'org.chromium.android_webview.shell.AwShellActivity', + 'android-webview-command-line', + None), + 'gtest': chrome.PackageInfo( + 'org.chromium.native_test', + 'org.chromium.native_test.NativeUnitTestActivity', + 'chrome-native-tests-command-line', + None), + 'components_browsertests': chrome.PackageInfo( + 'org.chromium.components_browsertests_apk', + ('org.chromium.components_browsertests_apk' + + '.ComponentsBrowserTestsActivity'), + 'chrome-native-tests-command-line', + None), + 'content_browsertests': chrome.PackageInfo( + 'org.chromium.content_browsertests_apk', + 'org.chromium.content_browsertests_apk.ContentBrowserTestsActivity', + 'chrome-native-tests-command-line', + None), + 'chromedriver_webview_shell': chrome.PackageInfo( + 'org.chromium.chromedriver_webview_shell', + 'org.chromium.chromedriver_webview_shell.Main', + None, + None), +}) + + +# Ports arrangement for various test servers used in Chrome for Android. +# Lighttpd server will attempt to use 9000 as default port, if unavailable it +# will find a free port from 8001 - 8999. +LIGHTTPD_DEFAULT_PORT = 9000 +LIGHTTPD_RANDOM_PORT_FIRST = 8001 +LIGHTTPD_RANDOM_PORT_LAST = 8999 +TEST_SYNC_SERVER_PORT = 9031 +TEST_SEARCH_BY_IMAGE_SERVER_PORT = 9041 +TEST_POLICY_SERVER_PORT = 9051 + + +TEST_EXECUTABLE_DIR = '/data/local/tmp' +# Directories for common java libraries for SDK build. +# These constants are defined in build/android/ant/common.xml +SDK_BUILD_JAVALIB_DIR = 'lib.java' +SDK_BUILD_TEST_JAVALIB_DIR = 'test.lib.java' +SDK_BUILD_APKS_DIR = 'apks' + +ADB_KEYS_FILE = '/data/misc/adb/adb_keys' + +PERF_OUTPUT_DIR = os.path.join(DIR_SOURCE_ROOT, 'out', 'step_results') +# The directory on the device where perf test output gets saved to. +DEVICE_PERF_OUTPUT_DIR = ( + '/data/data/' + PACKAGE_INFO['chrome'].package + '/files') + +SCREENSHOTS_DIR = os.path.join(DIR_SOURCE_ROOT, 'out_screenshots') + +ANDROID_SDK_VERSION = version_codes.MARSHMALLOW +ANDROID_SDK_BUILD_TOOLS_VERSION = '24.0.2' +ANDROID_SDK_ROOT = os.path.join(DIR_SOURCE_ROOT, + 'third_party', 'android_tools', 'sdk') +ANDROID_SDK_TOOLS = os.path.join(ANDROID_SDK_ROOT, + 'build-tools', ANDROID_SDK_BUILD_TOOLS_VERSION) +ANDROID_NDK_ROOT = os.path.join(DIR_SOURCE_ROOT, + 'third_party', 'android_tools', 'ndk') + +PROGUARD_SCRIPT_PATH = os.path.join( + ANDROID_SDK_ROOT, 'tools', 'proguard', 'bin', 'proguard.sh') + +PROGUARD_ROOT = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'proguard') + +BAD_DEVICES_JSON = os.path.join(DIR_SOURCE_ROOT, + os.environ.get('CHROMIUM_OUT_DIR', 'out'), + 'bad_devices.json') + +UPSTREAM_FLAKINESS_SERVER = 'test-results.appspot.com' + +# TODO(jbudorick): Remove once unused. +DEVICE_LOCAL_PROPERTIES_PATH = '/data/local.prop' + +# TODO(jbudorick): Rework this into testing/buildbot/ +PYTHON_UNIT_TEST_SUITES = { + 'pylib_py_unittests': { + 'path': os.path.join(DIR_SOURCE_ROOT, 'build', 'android'), + 'test_modules': [ + 'devil.android.device_utils_test', + 'devil.android.md5sum_test', + 'devil.utils.cmd_helper_test', + 'pylib.results.json_results_test', + 'pylib.utils.proguard_test', + ] + }, + 'gyp_py_unittests': { + 'path': os.path.join(DIR_SOURCE_ROOT, 'build', 'android', 'gyp'), + 'test_modules': [ + 'java_cpp_enum_tests', + 'java_google_api_keys_tests', + ] + }, +} + +LOCAL_MACHINE_TESTS = ['junit', 'python'] +VALID_ENVIRONMENTS = ['local'] +VALID_TEST_TYPES = ['gtest', 'instrumentation', 'junit', 'linker', 'monkey', + 'perf', 'python'] +VALID_DEVICE_TYPES = ['Android', 'iOS'] + + +def GetBuildType(): + try: + return os.environ['BUILDTYPE'] + except KeyError: + raise EnvironmentError( + 'The BUILDTYPE environment variable has not been set') + + +def SetBuildType(build_type): + os.environ['BUILDTYPE'] = build_type + + +def SetBuildDirectory(build_directory): + os.environ['CHROMIUM_OUT_DIR'] = build_directory + + +def SetOutputDirectory(output_directory): + os.environ['CHROMIUM_OUTPUT_DIR'] = output_directory + + +def GetOutDirectory(build_type=None): + """Returns the out directory where the output binaries are built. + + Args: + build_type: Build type, generally 'Debug' or 'Release'. Defaults to the + globally set build type environment variable BUILDTYPE. + """ + if 'CHROMIUM_OUTPUT_DIR' in os.environ: + return os.path.abspath(os.path.join( + DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUTPUT_DIR'))) + + return os.path.abspath(os.path.join( + DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUT_DIR', 'out'), + GetBuildType() if build_type is None else build_type)) + + +def CheckOutputDirectory(): + """Checks that CHROMIUM_OUT_DIR or CHROMIUM_OUTPUT_DIR is set. + + If neither are set, but the current working directory is a build directory, + then CHROMIUM_OUTPUT_DIR is set to the current working directory. + + Raises: + Exception: If no output directory is detected. + """ + output_dir = os.environ.get('CHROMIUM_OUTPUT_DIR') + out_dir = os.environ.get('CHROMIUM_OUT_DIR') + if not output_dir and not out_dir: + # If CWD is an output directory, then assume it's the desired one. + if os.path.exists('build.ninja'): + output_dir = os.getcwd() + SetOutputDirectory(output_dir) + elif os.environ.get('CHROME_HEADLESS'): + # When running on bots, see if the output directory is obvious. + dirs = glob.glob(os.path.join(DIR_SOURCE_ROOT, 'out', '*', 'build.ninja')) + if len(dirs) == 1: + SetOutputDirectory(dirs[0]) + else: + raise Exception('Neither CHROMIUM_OUTPUT_DIR nor CHROMIUM_OUT_DIR ' + 'has been set. CHROME_HEADLESS detected, but multiple ' + 'out dirs exist: %r' % dirs) + else: + raise Exception('Neither CHROMIUM_OUTPUT_DIR nor CHROMIUM_OUT_DIR ' + 'has been set') + + +# TODO(jbudorick): Convert existing callers to AdbWrapper.GetAdbPath() and +# remove this. +def GetAdbPath(): + from devil.android.sdk import adb_wrapper + return adb_wrapper.AdbWrapper.GetAdbPath() + + +# Exit codes +ERROR_EXIT_CODE = exit_codes.ERROR +INFRA_EXIT_CODE = exit_codes.INFRA +WARNING_EXIT_CODE = exit_codes.WARNING diff --git a/build/android/pylib/constants/host_paths.py b/build/android/pylib/constants/host_paths.py new file mode 100644 index 0000000000000000000000000000000000000000..98aa53dd0b966d8bf04aea98bac42069b88bc1ac --- /dev/null +++ b/build/android/pylib/constants/host_paths.py @@ -0,0 +1,38 @@ +# Copyright 2016 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 contextlib +import os +import sys + +DIR_SOURCE_ROOT = os.environ.get( + 'CHECKOUT_SOURCE_ROOT', + os.path.abspath(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, os.pardir, os.pardir))) + +BUILD_COMMON_PATH = os.path.join( + DIR_SOURCE_ROOT, 'build', 'util', 'lib', 'common') + +# third-party libraries +ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH = os.path.join( + DIR_SOURCE_ROOT, 'third_party', 'android_platform', 'development', + 'scripts') +DEVIL_PATH = os.path.join( + DIR_SOURCE_ROOT, 'third_party', 'catapult', 'devil') +PYMOCK_PATH = os.path.join( + DIR_SOURCE_ROOT, 'third_party', 'pymock') + +@contextlib.contextmanager +def SysPath(path, position=None): + if position is None: + sys.path.append(path) + else: + sys.path.insert(position, path) + try: + yield + finally: + if sys.path[-1] == path: + sys.path.pop() + else: + sys.path.remove(path) diff --git a/build/build_config.h b/build/build_config.h index 80a93d3d43d21890634aa562bcf66d2e59a90e9f..5ee96b18a141c2043f9fe73092870e7c4cdcb52f 100644 --- a/build/build_config.h +++ b/build/build_config.h @@ -6,6 +6,7 @@ // Operating System: // OS_WIN / OS_MACOSX / OS_LINUX / OS_POSIX (MACOSX or LINUX) / // OS_NACL (NACL_SFI or NACL_NONSFI) / OS_NACL_SFI / OS_NACL_NONSFI +// OS_CHROMEOS is set by the build system // Compiler: // COMPILER_MSVC / COMPILER_GCC // Processor: @@ -39,9 +40,6 @@ #if defined(__ANDROID__) // Android targets #define __linux__ 1 -#if defined(__BIONIC__) -#define __UCLIBC__ 1 -#endif // defined(__BIONIC__) #elif !defined(__ANDROID_HOST__) // Chrome OS @@ -84,9 +82,10 @@ #endif #elif defined(_WIN32) #define OS_WIN 1 -#define TOOLKIT_VIEWS 1 #elif defined(__FreeBSD__) #define OS_FREEBSD 1 +#elif defined(__NetBSD__) +#define OS_NETBSD 1 #elif defined(__OpenBSD__) #define OS_OPENBSD 1 #elif defined(__sun) @@ -103,15 +102,16 @@ // For access to standard BSD features, use OS_BSD instead of a // more specific macro. -#if defined(OS_FREEBSD) || defined(OS_OPENBSD) +#if defined(OS_FREEBSD) || defined(OS_NETBSD) || defined(OS_OPENBSD) #define OS_BSD 1 #endif // For access to standard POSIXish features, use OS_POSIX instead of a // more specific macro. #if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_FREEBSD) || \ - defined(OS_OPENBSD) || defined(OS_SOLARIS) || defined(OS_ANDROID) || \ - defined(OS_NACL) || defined(OS_QNX) + defined(OS_NETBSD) || defined(OS_OPENBSD) || defined(OS_SOLARIS) || \ + defined(OS_ANDROID) || defined(OS_OPENBSD) || defined(OS_SOLARIS) || \ + defined(OS_ANDROID) || defined(OS_NACL) || defined(OS_QNX) #define OS_POSIX 1 #endif @@ -144,6 +144,31 @@ #define ARCH_CPU_X86 1 #define ARCH_CPU_32_BITS 1 #define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__s390x__) +#define ARCH_CPU_S390_FAMILY 1 +#define ARCH_CPU_S390X 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__s390__) +#define ARCH_CPU_S390_FAMILY 1 +#define ARCH_CPU_S390 1 +#define ARCH_CPU_31_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__PPC64__) && defined(__BIG_ENDIAN__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__PPC64__) && defined(__LITTLE_ENDIAN__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__PPC__) +#define ARCH_CPU_PPC_FAMILY 1 +#define ARCH_CPU_PPC 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 #elif defined(__ARMEL__) #define ARCH_CPU_ARM_FAMILY 1 #define ARCH_CPU_ARMEL 1 diff --git a/build/gn_helpers.py b/build/gn_helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..33cc5786d9e0163159f80cb6c22c9b370c16253f --- /dev/null +++ b/build/gn_helpers.py @@ -0,0 +1,351 @@ +# Copyright 2014 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. + +"""Helper functions useful when writing scripts that integrate with GN. + +The main functions are ToGNString and FromGNString which convert between +serialized GN veriables and Python variables. + +To use in a random python file in the build: + + import os + import sys + + sys.path.append(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, "build")) + import gn_helpers + +Where the sequence of parameters to join is the relative path from your source +file to the build directory.""" + +class GNException(Exception): + pass + + +def ToGNString(value, allow_dicts = True): + """Returns a stringified GN equivalent of the Python value. + + allow_dicts indicates if this function will allow converting dictionaries + to GN scopes. This is only possible at the top level, you can't nest a + GN scope in a list, so this should be set to False for recursive calls.""" + if isinstance(value, basestring): + if value.find('\n') >= 0: + raise GNException("Trying to print a string with a newline in it.") + return '"' + \ + value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \ + '"' + + if isinstance(value, unicode): + return ToGNString(value.encode('utf-8')) + + if isinstance(value, bool): + if value: + return "true" + return "false" + + if isinstance(value, list): + return '[ %s ]' % ', '.join(ToGNString(v) for v in value) + + if isinstance(value, dict): + if not allow_dicts: + raise GNException("Attempting to recursively print a dictionary.") + result = "" + for key in sorted(value): + if not isinstance(key, basestring): + raise GNException("Dictionary key is not a string.") + result += "%s = %s\n" % (key, ToGNString(value[key], False)) + return result + + if isinstance(value, int): + return str(value) + + raise GNException("Unsupported type when printing to GN.") + + +def FromGNString(input_string): + """Converts the input string from a GN serialized value to Python values. + + For details on supported types see GNValueParser.Parse() below. + + If your GN script did: + something = [ "file1", "file2" ] + args = [ "--values=$something" ] + The command line would look something like: + --values="[ \"file1\", \"file2\" ]" + Which when interpreted as a command line gives the value: + [ "file1", "file2" ] + + You can parse this into a Python list using GN rules with: + input_values = FromGNValues(options.values) + Although the Python 'ast' module will parse many forms of such input, it + will not handle GN escaping properly, nor GN booleans. You should use this + function instead. + + + A NOTE ON STRING HANDLING: + + If you just pass a string on the command line to your Python script, or use + string interpolation on a string variable, the strings will not be quoted: + str = "asdf" + args = [ str, "--value=$str" ] + Will yield the command line: + asdf --value=asdf + The unquoted asdf string will not be valid input to this function, which + accepts only quoted strings like GN scripts. In such cases, you can just use + the Python string literal directly. + + The main use cases for this is for other types, in particular lists. When + using string interpolation on a list (as in the top example) the embedded + strings will be quoted and escaped according to GN rules so the list can be + re-parsed to get the same result.""" + parser = GNValueParser(input_string) + return parser.Parse() + + +def FromGNArgs(input_string): + """Converts a string with a bunch of gn arg assignments into a Python dict. + + Given a whitespace-separated list of + + = (integer | string | boolean | ) + + gn assignments, this returns a Python dict, i.e.: + + FromGNArgs("foo=true\nbar=1\n") -> { 'foo': True, 'bar': 1 }. + + Only simple types and lists supported; variables, structs, calls + and other, more complicated things are not. + + This routine is meant to handle only the simple sorts of values that + arise in parsing --args. + """ + parser = GNValueParser(input_string) + return parser.ParseArgs() + + +def UnescapeGNString(value): + """Given a string with GN escaping, returns the unescaped string. + + Be careful not to feed with input from a Python parsing function like + 'ast' because it will do Python unescaping, which will be incorrect when + fed into the GN unescaper.""" + result = '' + i = 0 + while i < len(value): + if value[i] == '\\': + if i < len(value) - 1: + next_char = value[i + 1] + if next_char in ('$', '"', '\\'): + # These are the escaped characters GN supports. + result += next_char + i += 1 + else: + # Any other backslash is a literal. + result += '\\' + else: + result += value[i] + i += 1 + return result + + +def _IsDigitOrMinus(char): + return char in "-0123456789" + + +class GNValueParser(object): + """Duplicates GN parsing of values and converts to Python types. + + Normally you would use the wrapper function FromGNValue() below. + + If you expect input as a specific type, you can also call one of the Parse* + functions directly. All functions throw GNException on invalid input. """ + def __init__(self, string): + self.input = string + self.cur = 0 + + def IsDone(self): + return self.cur == len(self.input) + + def ConsumeWhitespace(self): + while not self.IsDone() and self.input[self.cur] in ' \t\n': + self.cur += 1 + + def Parse(self): + """Converts a string representing a printed GN value to the Python type. + + See additional usage notes on FromGNString above. + + - GN booleans ('true', 'false') will be converted to Python booleans. + + - GN numbers ('123') will be converted to Python numbers. + + - GN strings (double-quoted as in '"asdf"') will be converted to Python + strings with GN escaping rules. GN string interpolation (embedded + variables preceeded by $) are not supported and will be returned as + literals. + + - GN lists ('[1, "asdf", 3]') will be converted to Python lists. + + - GN scopes ('{ ... }') are not supported.""" + result = self._ParseAllowTrailing() + self.ConsumeWhitespace() + if not self.IsDone(): + raise GNException("Trailing input after parsing:\n " + + self.input[self.cur:]) + return result + + def ParseArgs(self): + """Converts a whitespace-separated list of ident=literals to a dict. + + See additional usage notes on FromGNArgs, above. + """ + d = {} + + self.ConsumeWhitespace() + while not self.IsDone(): + ident = self._ParseIdent() + self.ConsumeWhitespace() + if self.input[self.cur] != '=': + raise GNException("Unexpected token: " + self.input[self.cur:]) + self.cur += 1 + self.ConsumeWhitespace() + val = self._ParseAllowTrailing() + self.ConsumeWhitespace() + d[ident] = val + + return d + + def _ParseAllowTrailing(self): + """Internal version of Parse that doesn't check for trailing stuff.""" + self.ConsumeWhitespace() + if self.IsDone(): + raise GNException("Expected input to parse.") + + next_char = self.input[self.cur] + if next_char == '[': + return self.ParseList() + elif _IsDigitOrMinus(next_char): + return self.ParseNumber() + elif next_char == '"': + return self.ParseString() + elif self._ConstantFollows('true'): + return True + elif self._ConstantFollows('false'): + return False + else: + raise GNException("Unexpected token: " + self.input[self.cur:]) + + def _ParseIdent(self): + ident = '' + + next_char = self.input[self.cur] + if not next_char.isalpha() and not next_char=='_': + raise GNException("Expected an identifier: " + self.input[self.cur:]) + + ident += next_char + self.cur += 1 + + next_char = self.input[self.cur] + while next_char.isalpha() or next_char.isdigit() or next_char=='_': + ident += next_char + self.cur += 1 + next_char = self.input[self.cur] + + return ident + + def ParseNumber(self): + self.ConsumeWhitespace() + if self.IsDone(): + raise GNException('Expected number but got nothing.') + + begin = self.cur + + # The first character can include a negative sign. + if not self.IsDone() and _IsDigitOrMinus(self.input[self.cur]): + self.cur += 1 + while not self.IsDone() and self.input[self.cur].isdigit(): + self.cur += 1 + + number_string = self.input[begin:self.cur] + if not len(number_string) or number_string == '-': + raise GNException("Not a valid number.") + return int(number_string) + + def ParseString(self): + self.ConsumeWhitespace() + if self.IsDone(): + raise GNException('Expected string but got nothing.') + + if self.input[self.cur] != '"': + raise GNException('Expected string beginning in a " but got:\n ' + + self.input[self.cur:]) + self.cur += 1 # Skip over quote. + + begin = self.cur + while not self.IsDone() and self.input[self.cur] != '"': + if self.input[self.cur] == '\\': + self.cur += 1 # Skip over the backslash. + if self.IsDone(): + raise GNException("String ends in a backslash in:\n " + + self.input) + self.cur += 1 + + if self.IsDone(): + raise GNException('Unterminated string:\n ' + self.input[begin:]) + + end = self.cur + self.cur += 1 # Consume trailing ". + + return UnescapeGNString(self.input[begin:end]) + + def ParseList(self): + self.ConsumeWhitespace() + if self.IsDone(): + raise GNException('Expected list but got nothing.') + + # Skip over opening '['. + if self.input[self.cur] != '[': + raise GNException("Expected [ for list but got:\n " + + self.input[self.cur:]) + self.cur += 1 + self.ConsumeWhitespace() + if self.IsDone(): + raise GNException("Unterminated list:\n " + self.input) + + list_result = [] + previous_had_trailing_comma = True + while not self.IsDone(): + if self.input[self.cur] == ']': + self.cur += 1 # Skip over ']'. + return list_result + + if not previous_had_trailing_comma: + raise GNException("List items not separated by comma.") + + list_result += [ self._ParseAllowTrailing() ] + self.ConsumeWhitespace() + if self.IsDone(): + break + + # Consume comma if there is one. + previous_had_trailing_comma = self.input[self.cur] == ',' + if previous_had_trailing_comma: + # Consume comma. + self.cur += 1 + self.ConsumeWhitespace() + + raise GNException("Unterminated list:\n " + self.input) + + def _ConstantFollows(self, constant): + """Returns true if the given constant follows immediately at the current + location in the input. If it does, the text is consumed and the function + returns true. Otherwise, returns false and the current position is + unchanged.""" + end = self.cur + len(constant) + if end > len(self.input): + return False # Not enough room. + if self.input[self.cur:end] == constant: + self.cur = end + return True + return False diff --git a/device/bluetooth/bluetooth_advertisement.cc b/device/bluetooth/bluetooth_advertisement.cc new file mode 100644 index 0000000000000000000000000000000000000000..05b0e52d2223154dcc3a4a9ac5c8aa5cc379ae7d --- /dev/null +++ b/device/bluetooth/bluetooth_advertisement.cc @@ -0,0 +1,37 @@ +// 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. + +#include "device/bluetooth/bluetooth_advertisement.h" + +namespace device { + +BluetoothAdvertisement::Data::Data(AdvertisementType type) + : type_(type), include_tx_power_(false) { +} + +BluetoothAdvertisement::Data::~Data() { +} + +BluetoothAdvertisement::Data::Data() + : type_(ADVERTISEMENT_TYPE_BROADCAST), include_tx_power_(false) { +} + +void BluetoothAdvertisement::AddObserver( + BluetoothAdvertisement::Observer* observer) { + CHECK(observer); + observers_.AddObserver(observer); +} + +void BluetoothAdvertisement::RemoveObserver( + BluetoothAdvertisement::Observer* observer) { + CHECK(observer); + observers_.RemoveObserver(observer); +} + +BluetoothAdvertisement::BluetoothAdvertisement() { +} +BluetoothAdvertisement::~BluetoothAdvertisement() { +} + +} // namespace device diff --git a/device/bluetooth/bluetooth_advertisement.h b/device/bluetooth/bluetooth_advertisement.h new file mode 100644 index 0000000000000000000000000000000000000000..412baa72eebf45e0d726532d5e51a53a2a4d882b --- /dev/null +++ b/device/bluetooth/bluetooth_advertisement.h @@ -0,0 +1,152 @@ +// 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. + +#ifndef DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_H_ + +#include + +#include +#include +#include +#include +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "device/bluetooth/bluetooth_export.h" + +namespace device { + +// BluetoothAdvertisement represents an advertisement which advertises over the +// LE channel during its lifetime. +class DEVICE_BLUETOOTH_EXPORT BluetoothAdvertisement + : public base::RefCounted { + public: + // Possible types of error raised while registering or unregistering + // advertisements. + enum ErrorCode { + ERROR_UNSUPPORTED_PLATFORM, // Bluetooth advertisement not supported on + // current platform. + ERROR_ADVERTISEMENT_ALREADY_EXISTS, // An advertisement is already + // registered. + ERROR_ADVERTISEMENT_DOES_NOT_EXIST, // Unregistering an advertisement which + // is not registered. + ERROR_ADVERTISEMENT_INVALID_LENGTH, // Advertisement is not of a valid + // length. +#if defined(OS_CHROMEOS) || defined(OS_LINUX) + ERROR_INVALID_ADVERTISEMENT_INTERVAL, // Advertisement interval specified + // is out of valid range. +#endif + INVALID_ADVERTISEMENT_ERROR_CODE + }; + + // Type of advertisement. + enum AdvertisementType { + // This advertises with the type set to ADV_NONCONN_IND, which indicates + // to receivers that our device is not connectable. + ADVERTISEMENT_TYPE_BROADCAST, + // This advertises with the type set to ADV_IND or ADV_SCAN_IND, which + // indicates to receivers that our device is connectable. + ADVERTISEMENT_TYPE_PERIPHERAL + }; + + using UUIDList = std::vector; + using ManufacturerData = std::map>; + using ServiceData = std::map>; + + // Structure that holds the data for an advertisement. + class DEVICE_BLUETOOTH_EXPORT Data { + public: + explicit Data(AdvertisementType type); + ~Data(); + + AdvertisementType type() { return type_; } + std::unique_ptr service_uuids() { + return std::move(service_uuids_); + } + std::unique_ptr manufacturer_data() { + return std::move(manufacturer_data_); + } + std::unique_ptr solicit_uuids() { + return std::move(solicit_uuids_); + } + std::unique_ptr service_data() { + return std::move(service_data_); + } + + void set_service_uuids(std::unique_ptr service_uuids) { + service_uuids_ = std::move(service_uuids); + } + void set_manufacturer_data( + std::unique_ptr manufacturer_data) { + manufacturer_data_ = std::move(manufacturer_data); + } + void set_solicit_uuids(std::unique_ptr solicit_uuids) { + solicit_uuids_ = std::move(solicit_uuids); + } + void set_service_data(std::unique_ptr service_data) { + service_data_ = std::move(service_data); + } + + void set_include_tx_power(bool include_tx_power) { + include_tx_power_ = include_tx_power; + } + + private: + Data(); + + AdvertisementType type_; + std::unique_ptr service_uuids_; + std::unique_ptr manufacturer_data_; + std::unique_ptr solicit_uuids_; + std::unique_ptr service_data_; + bool include_tx_power_; + + DISALLOW_COPY_AND_ASSIGN(Data); + }; + + // Interface for observing changes to this advertisement. + class Observer { + public: + virtual ~Observer() {} + + // Called when this advertisement is released and is no longer advertising. + virtual void AdvertisementReleased( + BluetoothAdvertisement* advertisement) = 0; + }; + + // Adds and removes observers for events for this advertisement. + void AddObserver(BluetoothAdvertisement::Observer* observer); + void RemoveObserver(BluetoothAdvertisement::Observer* observer); + + // Unregisters this advertisement. Called on destruction of this object + // automatically but can be called directly to explicitly unregister this + // object. + using SuccessCallback = base::Closure; + using ErrorCallback = base::Callback; + virtual void Unregister(const SuccessCallback& success_callback, + const ErrorCallback& error_callback) = 0; + + protected: + friend class base::RefCounted; + + BluetoothAdvertisement(); + + // The destructor will unregister this advertisement. + virtual ~BluetoothAdvertisement(); + + // List of observers interested in event notifications from us. Objects in + // |observers_| are expected to outlive a BluetoothAdvertisement object. + base::ObserverList observers_; + + private: + DISALLOW_COPY_AND_ASSIGN(BluetoothAdvertisement); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_H_ diff --git a/device/bluetooth/bluetooth_common.h b/device/bluetooth/bluetooth_common.h new file mode 100644 index 0000000000000000000000000000000000000000..6045980f9b4349f38942fb37021a163bfd6fd5a6 --- /dev/null +++ b/device/bluetooth/bluetooth_common.h @@ -0,0 +1,49 @@ +// Copyright 2016 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 DEVICE_BLUETOOTH_BLUETOOTH_TYPES_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_TYPES_H_ + +#include "device/bluetooth/bluetooth_export.h" + +// This file is for enums and small types common to several +// parts of bluetooth. + +namespace device { + +// Devices and adapters can support a number of transports, +// and bluetooth hosts can scan for devices based on the +// transports they support. +enum BluetoothTransport : uint8_t { + BLUETOOTH_TRANSPORT_INVALID = 0x00, + // Valid transports are given as a bitset. + BLUETOOTH_TRANSPORT_CLASSIC = 0x01, + BLUETOOTH_TRANSPORT_LE = 0x02, + BLUETOOTH_TRANSPORT_DUAL = + (BLUETOOTH_TRANSPORT_CLASSIC | BLUETOOTH_TRANSPORT_LE) +}; + +// Possible values that may be returned by BluetoothDevice::GetDeviceType(), +// representing different types of bluetooth device that we support or are aware +// of decoded from the bluetooth class information. +enum class BluetoothDeviceType { + UNKNOWN, + COMPUTER, + PHONE, + MODEM, + AUDIO, + CAR_AUDIO, + VIDEO, + PERIPHERAL, + JOYSTICK, + GAMEPAD, + KEYBOARD, + MOUSE, + TABLET, + KEYBOARD_MOUSE_COMBO +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_TYPES_H_ diff --git a/device/bluetooth/bluetooth_export.h b/device/bluetooth/bluetooth_export.h new file mode 100644 index 0000000000000000000000000000000000000000..90cc58cdb0f99241610c2c1d2ce0f1b76cfff424 --- /dev/null +++ b/device/bluetooth/bluetooth_export.h @@ -0,0 +1,28 @@ +// Copyright 2014 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 DEVICE_BLUETOOTH_DEVICE_BLUETOOTH_EXPORT_H_ +#define DEVICE_BLUETOOTH_DEVICE_BLUETOOTH_EXPORT_H_ + +#if defined(COMPONENT_BUILD) && defined(WIN32) + +#if defined(DEVICE_BLUETOOTH_IMPLEMENTATION) +#define DEVICE_BLUETOOTH_EXPORT __declspec(dllexport) +#else +#define DEVICE_BLUETOOTH_EXPORT __declspec(dllimport) +#endif + +#elif defined(COMPONENT_BUILD) && !defined(WIN32) + +#if defined(DEVICE_BLUETOOTH_IMPLEMENTATION) +#define DEVICE_BLUETOOTH_EXPORT __attribute__((visibility("default"))) +#else +#define DEVICE_BLUETOOTH_EXPORT +#endif + +#else +#define DEVICE_BLUETOOTH_EXPORT +#endif + +#endif // DEVICE_BLUETOOTH_DEVICE_BLUETOOTH_EXPORT_H_ diff --git a/device/bluetooth/bluetooth_uuid.cc b/device/bluetooth/bluetooth_uuid.cc new file mode 100644 index 0000000000000000000000000000000000000000..b35094deba169ab202a92d118bb5d02027b38c18 --- /dev/null +++ b/device/bluetooth/bluetooth_uuid.cc @@ -0,0 +1,98 @@ +// Copyright 2014 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. + +#include "device/bluetooth/bluetooth_uuid.h" + +#include + +#include "base/logging.h" +#include "base/strings/string_util.h" + +namespace device { + +namespace { + +const char kCommonUuidPostfix[] = "-0000-1000-8000-00805f9b34fb"; +const char kCommonUuidPrefix[] = "0000"; + +// Returns the canonical, 128-bit canonical, and the format of the UUID +// in |canonical|, |canonical_128|, and |format| based on |uuid|. +void GetCanonicalUuid(std::string uuid, + std::string* canonical, + std::string* canonical_128, + BluetoothUUID::Format* format) { + // Initialize the values for the failure case. + canonical->clear(); + canonical_128->clear(); + *format = BluetoothUUID::kFormatInvalid; + + if (uuid.empty()) + return; + + if (uuid.size() < 11 && + base::StartsWith(uuid, "0x", base::CompareCase::SENSITIVE)) { + uuid = uuid.substr(2); + } + + if (!(uuid.size() == 4 || uuid.size() == 8 || uuid.size() == 36)) + return; + + for (size_t i = 0; i < uuid.size(); ++i) { + if (i == 8 || i == 13 || i == 18 || i == 23) { + if (uuid[i] != '-') + return; + } else { + if (!base::IsHexDigit(uuid[i])) + return; + uuid[i] = base::ToLowerASCII(uuid[i]); + } + } + + canonical->assign(uuid); + if (uuid.size() == 4) { + canonical_128->assign(kCommonUuidPrefix + uuid + kCommonUuidPostfix); + *format = BluetoothUUID::kFormat16Bit; + } else if (uuid.size() == 8) { + canonical_128->assign(uuid + kCommonUuidPostfix); + *format = BluetoothUUID::kFormat32Bit; + } else { + canonical_128->assign(uuid); + *format = BluetoothUUID::kFormat128Bit; + } +} + +} // namespace + + +BluetoothUUID::BluetoothUUID(const std::string& uuid) { + GetCanonicalUuid(uuid, &value_, &canonical_value_, &format_); +} + +BluetoothUUID::BluetoothUUID() : format_(kFormatInvalid) { +} + +BluetoothUUID::~BluetoothUUID() { +} + +bool BluetoothUUID::IsValid() const { + return format_ != kFormatInvalid; +} + +bool BluetoothUUID::operator<(const BluetoothUUID& uuid) const { + return canonical_value_ < uuid.canonical_value_; +} + +bool BluetoothUUID::operator==(const BluetoothUUID& uuid) const { + return canonical_value_ == uuid.canonical_value_; +} + +bool BluetoothUUID::operator!=(const BluetoothUUID& uuid) const { + return canonical_value_ != uuid.canonical_value_; +} + +void PrintTo(const BluetoothUUID& uuid, std::ostream* out) { + *out << uuid.canonical_value(); +} + +} // namespace device diff --git a/device/bluetooth/bluetooth_uuid.h b/device/bluetooth/bluetooth_uuid.h new file mode 100644 index 0000000000000000000000000000000000000000..8487f6a2da552b10a289b70d6f0980e56661b373 --- /dev/null +++ b/device/bluetooth/bluetooth_uuid.h @@ -0,0 +1,106 @@ +// Copyright 2014 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 DEVICE_BLUETOOTH_BLUETOOTH_UUID_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_UUID_H_ + +#include + +#include "device/bluetooth/bluetooth_export.h" + +namespace device { + +// Opaque wrapper around a Bluetooth UUID. Instances of UUID represent the +// 128-bit universally unique identifiers (UUIDs) of profiles and attributes +// used in Bluetooth based communication, such as a peripheral's services, +// characteristics, and characteristic descriptors. An instance are +// constructed using a string representing 16, 32, or 128 bit UUID formats. +class DEVICE_BLUETOOTH_EXPORT BluetoothUUID { + public: + // Possible representation formats used during construction. + enum Format { + kFormatInvalid, + kFormat16Bit, + kFormat32Bit, + kFormat128Bit + }; + + // Single argument constructor. |uuid| can be a 16, 32, or 128 bit UUID + // represented as a 4, 8, or 36 character string with the following + // formats: + // xxxx + // 0xxxxx + // xxxxxxxx + // 0xxxxxxxxx + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // + // 16 and 32 bit UUIDs will be internally converted to a 128 bit UUID using + // the base UUID defined in the Bluetooth specification, hence custom UUIDs + // should be provided in the 128-bit format. If |uuid| is in an unsupported + // format, the result might be invalid. Use IsValid to check for validity + // after construction. + explicit BluetoothUUID(const std::string& uuid); + + // Default constructor does nothing. Since BluetoothUUID is copyable, this + // constructor is useful for initializing member variables and assigning a + // value to them later. The default constructor will initialize an invalid + // UUID by definition and the string accessors will return an empty string. + BluetoothUUID(); + virtual ~BluetoothUUID(); + + // Returns true, if the UUID is in a valid canonical format. + bool IsValid() const; + + // Returns the representation format of the UUID. This reflects the format + // that was provided during construction. + Format format() const { return format_; } + + // Returns the value of the UUID as a string. The representation format is + // based on what was passed in during construction. For the supported sizes, + // this representation can have the following formats: + // - 16 bit: xxxx + // - 32 bit: xxxxxxxx + // - 128 bit: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // where x is a lowercase hex digit. + const std::string& value() const { return value_; } + + // Returns the underlying 128-bit value as a string in the following format: + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // where x is a lowercase hex digit. + const std::string& canonical_value() const { return canonical_value_; } + + // Permit sufficient comparison to allow a UUID to be used as a key in a + // std::map. + bool operator<(const BluetoothUUID& uuid) const; + + // Equality operators. + bool operator==(const BluetoothUUID& uuid) const; + bool operator!=(const BluetoothUUID& uuid) const; + + private: + // String representation of the UUID that was used during construction. For + // the supported sizes, this representation can have the following formats: + // - 16 bit: xxxx + // - 32 bit: xxxxxxxx + // - 128 bit: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + Format format_; + std::string value_; + + // The 128-bit string representation of the UUID. + std::string canonical_value_; +}; + +// This is required by gtest to print a readable output on test failures. +void DEVICE_BLUETOOTH_EXPORT +PrintTo(const BluetoothUUID& uuid, std::ostream* out); + +struct BluetoothUUIDHash { + size_t operator()(const device::BluetoothUUID& uuid) const { + return std::hash()(uuid.canonical_value()); + } +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_UUID_H_ diff --git a/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc b/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc new file mode 100644 index 0000000000000000000000000000000000000000..ee6cbf611248c0dea5bb3a1306c14bdc4b70f1c2 --- /dev/null +++ b/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc @@ -0,0 +1,54 @@ +// Copyright 2016 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. + +#include "device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.h" + +#include + +#include "base/logging.h" +#include "base/memory/ptr_util.h" + +namespace bluez { + +BluetoothServiceAttributeValueBlueZ::BluetoothServiceAttributeValueBlueZ() + : type_(NULLTYPE), size_(0), value_(base::Value::CreateNullValue()) {} + +BluetoothServiceAttributeValueBlueZ::BluetoothServiceAttributeValueBlueZ( + Type type, + size_t size, + std::unique_ptr value) + : type_(type), size_(size), value_(std::move(value)) { + CHECK_NE(type, SEQUENCE); +} + +BluetoothServiceAttributeValueBlueZ::BluetoothServiceAttributeValueBlueZ( + std::unique_ptr sequence) + : type_(SEQUENCE), + size_(sequence->size()), + sequence_(std::move(sequence)) {} + +BluetoothServiceAttributeValueBlueZ::BluetoothServiceAttributeValueBlueZ( + const BluetoothServiceAttributeValueBlueZ& attribute) { + *this = attribute; +} + +BluetoothServiceAttributeValueBlueZ& BluetoothServiceAttributeValueBlueZ:: +operator=(const BluetoothServiceAttributeValueBlueZ& attribute) { + if (this != &attribute) { + type_ = attribute.type_; + size_ = attribute.size_; + if (attribute.type_ == SEQUENCE) { + value_ = nullptr; + sequence_ = base::MakeUnique(*attribute.sequence_); + } else { + value_ = attribute.value_->CreateDeepCopy(); + sequence_ = nullptr; + } + } + return *this; +} + +BluetoothServiceAttributeValueBlueZ::~BluetoothServiceAttributeValueBlueZ() {} + +} // namespace bluez diff --git a/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.h b/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.h new file mode 100644 index 0000000000000000000000000000000000000000..fdd291a5f100e4cdced1af2460235afdcf30b4ec --- /dev/null +++ b/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.h @@ -0,0 +1,59 @@ +// Copyright 2016 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 DEVICE_BLUETOOTH_BLUEZ_BLUETOOTH_SERVICE_ATTRIBUTE_VALUE_BLUEZ_H_ +#define DEVICE_BLUETOOTH_BLUEZ_BLUETOOTH_SERVICE_ATTRIBUTE_VALUE_BLUEZ_H_ + +#include +#include +#include + +#include "base/values.h" +#include "device/bluetooth/bluetooth_export.h" + +namespace bluez { + +// This class contains a Bluetooth service attribute. A service attribute is +// defined by the following fields, +// type: This is the type of the attribute. Along with being any of the +// fixed types, an attribute can also be of type sequence, which means +// that it contains an array of other attributes. +// size: This is the size of the attribute. This can be variable for each type. +// For example, a UUID can have the sizes, 2, 4 or 16 bytes. +// value: This is the raw value of the attribute. For example, for a UUID, it +// will be the string representation of the UUID. For a sequence, it +// will be an array of other attributes. +class DEVICE_BLUETOOTH_EXPORT BluetoothServiceAttributeValueBlueZ { + public: + enum Type { NULLTYPE = 0, UINT, INT, UUID, STRING, BOOL, SEQUENCE, URL }; + + using Sequence = std::vector; + + BluetoothServiceAttributeValueBlueZ(); + BluetoothServiceAttributeValueBlueZ(Type type, + size_t size, + std::unique_ptr value); + explicit BluetoothServiceAttributeValueBlueZ( + std::unique_ptr sequence); + BluetoothServiceAttributeValueBlueZ( + const BluetoothServiceAttributeValueBlueZ& attribute); + BluetoothServiceAttributeValueBlueZ& operator=( + const BluetoothServiceAttributeValueBlueZ& attribute); + ~BluetoothServiceAttributeValueBlueZ(); + + Type type() const { return type_; } + size_t size() const { return size_; } + const Sequence& sequence() const { return *sequence_.get(); } + const base::Value& value() const { return *value_.get(); } + + private: + Type type_; + size_t size_; + std::unique_ptr value_; + std::unique_ptr sequence_; +}; + +} // namespace bluez + +#endif // DEVICE_BLUETOOTH_BLUEZ_BLUETOOTH_SERVICE_ATTRIBUTE_VALUE_BLUEZ_H_ diff --git a/gen/mojo/common/common_custom_types__type_mappings b/gen/mojo/common/common_custom_types__type_mappings new file mode 100644 index 0000000000000000000000000000000000000000..2e5cd5d93cf3573a0d39b966addeb24fd4f1e2cb --- /dev/null +++ b/gen/mojo/common/common_custom_types__type_mappings @@ -0,0 +1,193 @@ +{ + "c++": { + "mojo.common.mojom.Value": { + "hashable": false, + "typename": "std::unique_ptr", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/values_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": true, + "nullable_is_same_type": true, + "non_copyable_non_movable": false, + "public_headers": [ + "base/values.h" + ] + }, + "mojo.common.mojom.UnguessableToken": { + "hashable": false, + "typename": "base::UnguessableToken", + "traits_headers": [ + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/unguessable_token.h" + ] + }, + "mojo.common.mojom.TextDirection": { + "hashable": false, + "typename": "base::i18n::TextDirection", + "traits_headers": [ + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/i18n/rtl.h" + ] + }, + "mojo.common.mojom.ListValue": { + "hashable": false, + "typename": "std::unique_ptr", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/values_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": true, + "nullable_is_same_type": true, + "non_copyable_non_movable": false, + "public_headers": [ + "base/values.h" + ] + }, + "mojo.common.mojom.String16": { + "hashable": false, + "typename": "base::string16", + "traits_headers": [ + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/strings/string16.h" + ] + }, + "mojo.common.mojom.Time": { + "hashable": false, + "typename": "base::Time", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": true, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/time/time.h" + ] + }, + "mojo.common.mojom.TimeDelta": { + "hashable": false, + "typename": "base::TimeDelta", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": true, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/time/time.h" + ] + }, + "mojo.common.mojom.TimeTicks": { + "hashable": false, + "typename": "base::TimeTicks", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": true, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/time/time.h" + ] + }, + "mojo.common.mojom.LegacyListValue": { + "hashable": false, + "typename": "base::ListValue", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/values_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": true, + "public_headers": [ + "base/values.h" + ] + }, + "mojo.common.mojom.File": { + "hashable": false, + "typename": "base::File", + "traits_headers": [ + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": true, + "nullable_is_same_type": true, + "non_copyable_non_movable": false, + "public_headers": [ + "base/files/file.h" + ] + }, + "mojo.common.mojom.FilePath": { + "hashable": false, + "typename": "base::FilePath", + "traits_headers": [ + "ipc/ipc_message_utils.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/files/file_path.h" + ] + }, + "mojo.common.mojom.DictionaryValue": { + "hashable": false, + "typename": "std::unique_ptr", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/values_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": true, + "nullable_is_same_type": true, + "non_copyable_non_movable": false, + "public_headers": [ + "base/values.h" + ] + }, + "mojo.common.mojom.Version": { + "hashable": false, + "typename": "base::Version", + "traits_headers": [ + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/version.h" + ] + } + } +} diff --git a/ipc/ipc.mojom b/ipc/ipc.mojom new file mode 100644 index 0000000000000000000000000000000000000000..0a4fcfa84e60f05925955604007d4830376a7f94 --- /dev/null +++ b/ipc/ipc.mojom @@ -0,0 +1,40 @@ +// Copyright 2016 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. + +module IPC.mojom; + +// NOTE: This MUST match the value of MSG_ROUTING_NONE in src/ipc/ipc_message.h. +const int32 kRoutingIdNone = -2; + +struct SerializedHandle { + handle the_handle; + + enum Type { + MOJO_HANDLE, + PLATFORM_FILE, + WIN_HANDLE, + MACH_PORT, + }; + + Type type; +}; + +// A placeholder interface type since we don't yet support generic associated +// message pipe handles. +interface GenericInterface {}; + +interface Channel { + // Informs the remote end of this client's PID. Must be called exactly once, + // before any calls to Receive() below. + SetPeerPid(int32 pid); + + // Transmits a classical Chrome IPC message. + Receive(array data, array? handles); + + // Requests a Channel-associated interface. + GetAssociatedInterface(string name, associated GenericInterface& request); +}; + +// A strictly nominal interface used to identify Channel bootstrap requests. +interface ChannelBootstrap {}; diff --git a/ipc/ipc_channel_handle.h b/ipc/ipc_channel_handle.h new file mode 100644 index 0000000000000000000000000000000000000000..ef31b8434c4ef6de1c43bd4ff1afe5fa6b593920 --- /dev/null +++ b/ipc/ipc_channel_handle.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 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 IPC_IPC_CHANNEL_HANDLE_H_ +#define IPC_IPC_CHANNEL_HANDLE_H_ + +#include + +#include "build/build_config.h" +#include "mojo/public/cpp/system/message_pipe.h" + +#if defined(OS_NACL_SFI) +#include "base/file_descriptor_posix.h" +#endif // defined (OS_NACL_SFI) + +namespace IPC { + +// Note that serialization for this object is defined in the ParamTraits +// template specialization in ipc_message_utils.h. +#if defined(OS_NACL_SFI) +struct ChannelHandle { + ChannelHandle() {} + explicit ChannelHandle(const base::FileDescriptor& s) : socket(s) {} + + base::FileDescriptor socket; +}; +#else +struct ChannelHandle { + ChannelHandle() {} + ChannelHandle(mojo::MessagePipeHandle h) : mojo_handle(h) {} + + bool is_mojo_channel_handle() const { return mojo_handle.is_valid(); } + + mojo::MessagePipeHandle mojo_handle; +}; +#endif // defined(OS_NACL_SFI) + +} // namespace IPC + +#endif // IPC_IPC_CHANNEL_HANDLE_H_ diff --git a/ipc/ipc_export.h b/ipc/ipc_export.h new file mode 100644 index 0000000000000000000000000000000000000000..e1cbe8891f15ef980c9da85bc3ca00614e0cd996 --- /dev/null +++ b/ipc/ipc_export.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 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 IPC_IPC_EXPORT_H_ +#define IPC_IPC_EXPORT_H_ + +// Defines IPC_EXPORT so that functionality implemented by the IPC module can be +// exported to consumers. + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(IPC_IMPLEMENTATION) +#define IPC_EXPORT __declspec(dllexport) +#else +#define IPC_EXPORT __declspec(dllimport) +#endif // defined(IPC_IMPLEMENTATION) + +#else // defined(WIN32) + +#if defined(IPC_IMPLEMENTATION) +#define IPC_EXPORT __attribute__((visibility("default"))) +#else +#define IPC_EXPORT +#endif + +#endif + +#else // defined(COMPONENT_BUILD) +#define IPC_EXPORT +#endif + +#endif // IPC_IPC_EXPORT_H_ diff --git a/ipc/ipc_listener.h b/ipc/ipc_listener.h new file mode 100644 index 0000000000000000000000000000000000000000..d7ad75c321b93fb9b92ba2fa5b0f2cf0f729312f --- /dev/null +++ b/ipc/ipc_listener.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012 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 IPC_IPC_LISTENER_H_ +#define IPC_IPC_LISTENER_H_ + +#include + +#include + +#include "build/build_config.h" +#include "ipc/ipc_export.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace IPC { + +class Message; + +// Implemented by consumers of a Channel to receive messages. +class IPC_EXPORT Listener { + public: + // Called when a message is received. Returns true iff the message was + // handled. + virtual bool OnMessageReceived(const Message& message) = 0; + + // Called when the channel is connected and we have received the internal + // Hello message from the peer. + virtual void OnChannelConnected(int32_t peer_pid) {} + + // Called when an error is detected that causes the channel to close. + // This method is not called when a channel is closed normally. + virtual void OnChannelError() {} + + // Called when a message's deserialization failed. + virtual void OnBadMessageReceived(const Message& message) {} + + // Called when an associated interface request is received on a Channel and + // the Channel has no registered handler for it. + virtual void OnAssociatedInterfaceRequest( + const std::string& interface_name, + mojo::ScopedInterfaceEndpointHandle handle) {} + +#if defined(OS_POSIX) + // Called on the server side when a channel that listens for connections + // denies an attempt to connect. + virtual void OnChannelDenied() {} + + // Called on the server side when a channel that listens for connections + // has an error that causes the listening channel to close. + virtual void OnChannelListenError() {} +#endif // OS_POSIX + + protected: + virtual ~Listener() {} +}; + +} // namespace IPC + +#endif // IPC_IPC_LISTENER_H_ diff --git a/ipc/ipc_message.cc b/ipc/ipc_message.cc new file mode 100644 index 0000000000000000000000000000000000000000..f5e9ac7a8b807cd04842aa8139a5b9b8cfb0b9ee --- /dev/null +++ b/ipc/ipc_message.cc @@ -0,0 +1,205 @@ +// Copyright (c) 2012 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. + +#include "ipc/ipc_message.h" + +#include +#include +#include + +#include "base/atomic_sequence_num.h" +#include "base/logging.h" +#include "build/build_config.h" +#include "ipc/ipc_message_attachment.h" +#include "ipc/ipc_message_attachment_set.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#include "ipc/ipc_platform_file_attachment_posix.h" +#endif + +namespace { + +base::StaticAtomicSequenceNumber g_ref_num; + +// Create a reference number for identifying IPC messages in traces. The return +// values has the reference number stored in the upper 24 bits, leaving the low +// 8 bits set to 0 for use as flags. +inline uint32_t GetRefNumUpper24() { + base::trace_event::TraceLog* trace_log = + base::trace_event::TraceLog::GetInstance(); + uint32_t pid = trace_log ? trace_log->process_id() : 0; + uint32_t count = g_ref_num.GetNext(); + // The 24 bit hash is composed of 14 bits of the count and 10 bits of the + // Process ID. With the current trace event buffer cap, the 14-bit count did + // not appear to wrap during a trace. Note that it is not a big deal if + // collisions occur, as this is only used for debugging and trace analysis. + return ((pid << 14) | (count & 0x3fff)) << 8; +} + +} // namespace + +namespace IPC { + +//------------------------------------------------------------------------------ + +Message::~Message() { +} + +Message::Message() : base::Pickle(sizeof(Header)) { + header()->routing = header()->type = 0; + header()->flags = GetRefNumUpper24(); +#if defined(OS_POSIX) + header()->num_fds = 0; + header()->pad = 0; +#endif + Init(); +} + +Message::Message(int32_t routing_id, uint32_t type, PriorityValue priority) + : base::Pickle(sizeof(Header)) { + header()->routing = routing_id; + header()->type = type; + DCHECK((priority & 0xffffff00) == 0); + header()->flags = priority | GetRefNumUpper24(); +#if defined(OS_POSIX) + header()->num_fds = 0; + header()->pad = 0; +#endif + Init(); +} + +Message::Message(const char* data, int data_len) + : base::Pickle(data, data_len) { + Init(); +} + +Message::Message(const Message& other) : base::Pickle(other) { + Init(); + attachment_set_ = other.attachment_set_; +} + +void Message::Init() { + dispatch_error_ = false; +#ifdef IPC_MESSAGE_LOG_ENABLED + received_time_ = 0; + dont_log_ = false; + log_data_ = NULL; +#endif +} + +Message& Message::operator=(const Message& other) { + *static_cast(this) = other; + attachment_set_ = other.attachment_set_; + return *this; +} + +void Message::SetHeaderValues(int32_t routing, uint32_t type, uint32_t flags) { + // This should only be called when the message is already empty. + DCHECK(payload_size() == 0); + + header()->routing = routing; + header()->type = type; + header()->flags = flags; +} + +void Message::EnsureMessageAttachmentSet() { + if (attachment_set_.get() == NULL) + attachment_set_ = new MessageAttachmentSet; +} + +#ifdef IPC_MESSAGE_LOG_ENABLED +void Message::set_sent_time(int64_t time) { + DCHECK((header()->flags & HAS_SENT_TIME_BIT) == 0); + header()->flags |= HAS_SENT_TIME_BIT; + WriteInt64(time); +} + +int64_t Message::sent_time() const { + if ((header()->flags & HAS_SENT_TIME_BIT) == 0) + return 0; + + const char* data = end_of_payload(); + data -= sizeof(int64_t); + return *(reinterpret_cast(data)); +} + +void Message::set_received_time(int64_t time) const { + received_time_ = time; +} +#endif + +Message::NextMessageInfo::NextMessageInfo() + : message_size(0), message_found(false), pickle_end(nullptr), + message_end(nullptr) {} +Message::NextMessageInfo::~NextMessageInfo() {} + +// static +void Message::FindNext(const char* range_start, + const char* range_end, + NextMessageInfo* info) { + DCHECK(info); + info->message_found = false; + info->message_size = 0; + + size_t pickle_size = 0; + if (!base::Pickle::PeekNext(sizeof(Header), + range_start, range_end, &pickle_size)) + return; + + bool have_entire_pickle = + static_cast(range_end - range_start) >= pickle_size; + + info->message_size = pickle_size; + + if (!have_entire_pickle) + return; + + const char* pickle_end = range_start + pickle_size; + + info->message_end = pickle_end; + + info->pickle_end = pickle_end; + info->message_found = true; +} + +bool Message::WriteAttachment( + scoped_refptr attachment) { + size_t index; + bool success = attachment_set()->AddAttachment( + make_scoped_refptr(static_cast(attachment.get())), + &index); + DCHECK(success); + + // NOTE: If you add more data to the pickle, make sure to update + // PickleSizer::AddAttachment. + + // Write the index of the descriptor so that we don't have to + // keep the current descriptor as extra decoding state when deserialising. + WriteInt(static_cast(index)); + + return success; +} + +bool Message::ReadAttachment( + base::PickleIterator* iter, + scoped_refptr* attachment) const { + int index; + if (!iter->ReadInt(&index)) + return false; + + MessageAttachmentSet* attachment_set = attachment_set_.get(); + if (!attachment_set) + return false; + + *attachment = attachment_set->GetAttachmentAt(index); + + return nullptr != attachment->get(); +} + +bool Message::HasAttachments() const { + return attachment_set_.get() && !attachment_set_->empty(); +} + +} // namespace IPC diff --git a/ipc/ipc_message.h b/ipc/ipc_message.h new file mode 100644 index 0000000000000000000000000000000000000000..43e9ae39c13d967f7054ac949f7540602df74c8d --- /dev/null +++ b/ipc/ipc_message.h @@ -0,0 +1,305 @@ +// Copyright (c) 2012 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 IPC_IPC_MESSAGE_H_ +#define IPC_IPC_MESSAGE_H_ + +#include +#include + +#include + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/pickle.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "ipc/ipc_export.h" + +#if !defined(NDEBUG) +#define IPC_MESSAGE_LOG_ENABLED +#endif + +namespace IPC { + +namespace internal { +class ChannelReader; +} // namespace internal + +//------------------------------------------------------------------------------ + +struct LogData; +class MessageAttachmentSet; + +class IPC_EXPORT Message : public base::Pickle { + public: + enum PriorityValue { + PRIORITY_LOW = 1, + PRIORITY_NORMAL, + PRIORITY_HIGH + }; + + // Bit values used in the flags field. + // Upper 24 bits of flags store a reference number, so this enum is limited to + // 8 bits. + enum { + PRIORITY_MASK = 0x03, // Low 2 bits of store the priority value. + SYNC_BIT = 0x04, + REPLY_BIT = 0x08, + REPLY_ERROR_BIT = 0x10, + UNBLOCK_BIT = 0x20, + PUMPING_MSGS_BIT = 0x40, + HAS_SENT_TIME_BIT = 0x80, + }; + + ~Message() override; + + Message(); + + // Initialize a message with a user-defined type, priority value, and + // destination WebView ID. + Message(int32_t routing_id, uint32_t type, PriorityValue priority); + + // Initializes a message from a const block of data. The data is not copied; + // instead the data is merely referenced by this message. Only const methods + // should be used on the message when initialized this way. + Message(const char* data, int data_len); + + Message(const Message& other); + Message& operator=(const Message& other); + + PriorityValue priority() const { + return static_cast(header()->flags & PRIORITY_MASK); + } + + // True if this is a synchronous message. + void set_sync() { + header()->flags |= SYNC_BIT; + } + bool is_sync() const { + return (header()->flags & SYNC_BIT) != 0; + } + + // Set this on a reply to a synchronous message. + void set_reply() { + header()->flags |= REPLY_BIT; + } + + bool is_reply() const { + return (header()->flags & REPLY_BIT) != 0; + } + + // Set this on a reply to a synchronous message to indicate that no receiver + // was found. + void set_reply_error() { + header()->flags |= REPLY_ERROR_BIT; + } + + bool is_reply_error() const { + return (header()->flags & REPLY_ERROR_BIT) != 0; + } + + // Normally when a receiver gets a message and they're blocked on a + // synchronous message Send, they buffer a message. Setting this flag causes + // the receiver to be unblocked and the message to be dispatched immediately. + void set_unblock(bool unblock) { + if (unblock) { + header()->flags |= UNBLOCK_BIT; + } else { + header()->flags &= ~UNBLOCK_BIT; + } + } + + bool should_unblock() const { + return (header()->flags & UNBLOCK_BIT) != 0; + } + + // Tells the receiver that the caller is pumping messages while waiting + // for the result. + bool is_caller_pumping_messages() const { + return (header()->flags & PUMPING_MSGS_BIT) != 0; + } + + void set_dispatch_error() const { + dispatch_error_ = true; + } + + bool dispatch_error() const { + return dispatch_error_; + } + + uint32_t type() const { + return header()->type; + } + + int32_t routing_id() const { + return header()->routing; + } + + void set_routing_id(int32_t new_id) { + header()->routing = new_id; + } + + uint32_t flags() const { + return header()->flags; + } + + // Sets all the given header values. The message should be empty at this + // call. + void SetHeaderValues(int32_t routing, uint32_t type, uint32_t flags); + + template + static bool Dispatch(const Message* msg, T* obj, S* sender, P* parameter, + void (T::*func)()) { + (obj->*func)(); + return true; + } + + template + static bool Dispatch(const Message* msg, T* obj, S* sender, P* parameter, + void (T::*func)(P*)) { + (obj->*func)(parameter); + return true; + } + + // Used for async messages with no parameters. + static void Log(std::string* name, const Message* msg, std::string* l) { + } + + // The static method FindNext() returns several pieces of information, which + // are aggregated into an instance of this struct. + struct IPC_EXPORT NextMessageInfo { + NextMessageInfo(); + ~NextMessageInfo(); + + // Total message size. Always valid if |message_found| is true. + // If |message_found| is false but we could determine message size + // from the header, this field is non-zero. Otherwise it's zero. + size_t message_size; + // Whether an entire message was found in the given memory range. + bool message_found; + // Only filled in if |message_found| is true. + // The start address is passed into FindNext() by the caller, so isn't + // repeated in this struct. The end address of the pickle should be used to + // construct a base::Pickle. + const char* pickle_end; + // Only filled in if |message_found| is true. + // The end address of the message should be used to determine the start + // address of the next message. + const char* message_end; + }; + + // |info| is an output parameter and must not be nullptr. + static void FindNext(const char* range_start, + const char* range_end, + NextMessageInfo* info); + + // WriteAttachment appends |attachment| to the end of the set. It returns + // false iff the set is full. + bool WriteAttachment( + scoped_refptr attachment) override; + // ReadAttachment parses an attachment given the parsing state |iter| and + // writes it to |*attachment|. It returns true on success. + bool ReadAttachment( + base::PickleIterator* iter, + scoped_refptr* attachment) const override; + // Returns true if there are any attachment in this message. + bool HasAttachments() const override; + +#ifdef IPC_MESSAGE_LOG_ENABLED + // Adds the outgoing time from Time::Now() at the end of the message and sets + // a bit to indicate that it's been added. + void set_sent_time(int64_t time); + int64_t sent_time() const; + + void set_received_time(int64_t time) const; + int64_t received_time() const { return received_time_; } + void set_output_params(const std::string& op) const { output_params_ = op; } + const std::string& output_params() const { return output_params_; } + // The following four functions are needed so we can log sync messages with + // delayed replies. We stick the log data from the sent message into the + // reply message, so that when it's sent and we have the output parameters + // we can log it. As such, we set a flag on the sent message to not log it. + void set_sync_log_data(LogData* data) const { log_data_ = data; } + LogData* sync_log_data() const { return log_data_; } + void set_dont_log() const { dont_log_ = true; } + bool dont_log() const { return dont_log_; } +#endif + + protected: + friend class Channel; + friend class ChannelMojo; + friend class ChannelNacl; + friend class ChannelPosix; + friend class ChannelWin; + friend class internal::ChannelReader; + friend class MessageReplyDeserializer; + friend class SyncMessage; + +#pragma pack(push, 4) + struct Header : base::Pickle::Header { + int32_t routing; // ID of the view that this message is destined for + uint32_t type; // specifies the user-defined message type + uint32_t flags; // specifies control flags for the message +#if defined(OS_POSIX) + uint16_t num_fds; // the number of descriptors included with this message + uint16_t pad; // explicitly initialize this to appease valgrind +#endif + }; +#pragma pack(pop) + + Header* header() { + return headerT
(); + } + const Header* header() const { + return headerT
(); + } + + void Init(); + + // Used internally to support IPC::Listener::OnBadMessageReceived. + mutable bool dispatch_error_; + + // The set of file descriptors associated with this message. + scoped_refptr attachment_set_; + + // Ensure that a MessageAttachmentSet is allocated + void EnsureMessageAttachmentSet(); + + MessageAttachmentSet* attachment_set() { + EnsureMessageAttachmentSet(); + return attachment_set_.get(); + } + const MessageAttachmentSet* attachment_set() const { + return attachment_set_.get(); + } + +#ifdef IPC_MESSAGE_LOG_ENABLED + // Used for logging. + mutable int64_t received_time_; + mutable std::string output_params_; + mutable LogData* log_data_; + mutable bool dont_log_; +#endif + + FRIEND_TEST_ALL_PREFIXES(IPCMessageTest, FindNext); + FRIEND_TEST_ALL_PREFIXES(IPCMessageTest, FindNextOverflow); +}; + +//------------------------------------------------------------------------------ + +} // namespace IPC + +enum SpecialRoutingIDs { + // indicates that we don't have a routing ID yet. + MSG_ROUTING_NONE = -2, + + // indicates a general message not sent to a particular tab. + MSG_ROUTING_CONTROL = INT32_MAX, +}; + +#define IPC_REPLY_ID 0xFFFFFFF0 // Special message id for replies +#define IPC_LOGGING_ID 0xFFFFFFF1 // Special message id for logging + +#endif // IPC_IPC_MESSAGE_H_ diff --git a/ipc/ipc_message_attachment.cc b/ipc/ipc_message_attachment.cc new file mode 100644 index 0000000000000000000000000000000000000000..83440ae8e008847eebd411f8ffd08c69d9f529fe --- /dev/null +++ b/ipc/ipc_message_attachment.cc @@ -0,0 +1,15 @@ +// Copyright (c) 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. + +#include "ipc/ipc_message_attachment.h" + +namespace IPC { + +MessageAttachment::MessageAttachment() { +} + +MessageAttachment::~MessageAttachment() { +} + +} // namespace IPC diff --git a/ipc/ipc_message_attachment.h b/ipc/ipc_message_attachment.h new file mode 100644 index 0000000000000000000000000000000000000000..9ff1de8c32f53b6b8ce709ab3891ae2e2398b69e --- /dev/null +++ b/ipc/ipc_message_attachment.h @@ -0,0 +1,36 @@ +// Copyright (c) 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. + +#ifndef IPC_IPC_MESSAGE_ATTACHMENT_H_ +#define IPC_IPC_MESSAGE_ATTACHMENT_H_ + +#include "base/files/file.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/pickle.h" +#include "build/build_config.h" +#include "ipc/ipc.mojom.h" +#include "ipc/ipc_export.h" + +namespace IPC { + +// Auxiliary data sent with |Message|. This can be a platform file descriptor +// or a mojo |MessagePipe|. |GetType()| returns the type of the subclass. +class IPC_EXPORT MessageAttachment : public base::Pickle::Attachment { + public: + using Type = mojom::SerializedHandle::Type; + + virtual Type GetType() const = 0; + + protected: + friend class base::RefCountedThreadSafe; + MessageAttachment(); + ~MessageAttachment() override; + + DISALLOW_COPY_AND_ASSIGN(MessageAttachment); +}; + +} // namespace IPC + +#endif // IPC_IPC_MESSAGE_ATTACHMENT_H_ diff --git a/ipc/ipc_message_attachment_set.cc b/ipc/ipc_message_attachment_set.cc new file mode 100644 index 0000000000000000000000000000000000000000..b9a990da8bb6af5e498b70ffe64bef3fb9bdb6f2 --- /dev/null +++ b/ipc/ipc_message_attachment_set.cc @@ -0,0 +1,136 @@ +// Copyright (c) 2011 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. + +#include "ipc/ipc_message_attachment_set.h" + +#include + +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "ipc/ipc_message_attachment.h" + +namespace IPC { + +namespace { + +unsigned count_attachments_of_type( + const std::vector>& attachments, + MessageAttachment::Type type) { + unsigned count = 0; + for (const scoped_refptr& attachment : attachments) { + if (attachment->GetType() == type) + ++count; + } + return count; +} + +} // namespace + +MessageAttachmentSet::MessageAttachmentSet() + : consumed_descriptor_highwater_(0) { +} + +MessageAttachmentSet::~MessageAttachmentSet() { + if (consumed_descriptor_highwater_ == size()) + return; + + // We close all the owning descriptors. If this message should have + // been transmitted, then closing those with close flags set mirrors + // the expected behaviour. + // + // If this message was received with more descriptors than expected + // (which could a DOS against the browser by a rogue renderer) then all + // the descriptors have their close flag set and we free all the extra + // kernel resources. + LOG(WARNING) << "MessageAttachmentSet destroyed with unconsumed attachments: " + << consumed_descriptor_highwater_ << "/" << size(); +} + +unsigned MessageAttachmentSet::num_descriptors() const { + return count_attachments_of_type(attachments_, + MessageAttachment::Type::PLATFORM_FILE); +} + +unsigned MessageAttachmentSet::size() const { + return static_cast(attachments_.size()); +} + +bool MessageAttachmentSet::AddAttachment( + scoped_refptr attachment, + size_t* index) { +#if defined(OS_POSIX) + if (attachment->GetType() == MessageAttachment::Type::PLATFORM_FILE && + num_descriptors() == kMaxDescriptorsPerMessage) { + DLOG(WARNING) << "Cannot add file descriptor. MessageAttachmentSet full."; + return false; + } +#endif + + switch (attachment->GetType()) { + case MessageAttachment::Type::PLATFORM_FILE: + case MessageAttachment::Type::MOJO_HANDLE: + case MessageAttachment::Type::WIN_HANDLE: + case MessageAttachment::Type::MACH_PORT: + attachments_.push_back(attachment); + *index = attachments_.size() - 1; + return true; + } + return false; +} + +bool MessageAttachmentSet::AddAttachment( + scoped_refptr attachment) { + size_t index; + return AddAttachment(attachment, &index); +} + +scoped_refptr MessageAttachmentSet::GetAttachmentAt( + unsigned index) { + if (index >= size()) { + DLOG(WARNING) << "Accessing out of bound index:" << index << "/" << size(); + return scoped_refptr(); + } + + // We should always walk the descriptors in order, so it's reasonable to + // enforce this. Consider the case where a compromised renderer sends us + // the following message: + // + // ExampleMsg: + // num_fds:2 msg:FD(index = 1) control:SCM_RIGHTS {n, m} + // + // Here the renderer sent us a message which should have a descriptor, but + // actually sent two in an attempt to fill our fd table and kill us. By + // setting the index of the descriptor in the message to 1 (it should be + // 0), we would record a highwater of 1 and then consider all the + // descriptors to have been used. + // + // So we can either track of the use of each descriptor in a bitset, or we + // can enforce that we walk the indexes strictly in order. + // + // There's one more wrinkle: When logging messages, we may reparse them. So + // we have an exception: When the consumed_descriptor_highwater_ is at the + // end of the array and index 0 is requested, we reset the highwater value. + // TODO(morrita): This is absurd. This "wringle" disallow to introduce clearer + // ownership model. Only client is NaclIPCAdapter. See crbug.com/415294 + if (index == 0 && consumed_descriptor_highwater_ == size()) { + consumed_descriptor_highwater_ = 0; + } + + if (index != consumed_descriptor_highwater_) + return scoped_refptr(); + + consumed_descriptor_highwater_ = index + 1; + + return attachments_[index]; +} + +void MessageAttachmentSet::CommitAllDescriptors() { + attachments_.clear(); + consumed_descriptor_highwater_ = 0; +} + +} // namespace IPC diff --git a/ipc/ipc_message_attachment_set.h b/ipc/ipc_message_attachment_set.h new file mode 100644 index 0000000000000000000000000000000000000000..de37211435867ef794b07007103ebc42c31bd680 --- /dev/null +++ b/ipc/ipc_message_attachment_set.h @@ -0,0 +1,96 @@ +// Copyright (c) 2011 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 IPC_IPC_MESSAGE_ATTACHMENT_SET_H_ +#define IPC_IPC_MESSAGE_ATTACHMENT_SET_H_ + +#include + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "build/build_config.h" +#include "ipc/ipc_export.h" + +namespace IPC { + +class MessageAttachment; + +// ----------------------------------------------------------------------------- +// A MessageAttachmentSet is an ordered set of MessageAttachment objects +// associated with an IPC message. All attachments are wrapped in a mojo handle +// if necessary and sent over the mojo message pipe. +// +// For ChannelNacl under SFI NaCl, only Type::PLATFORM_FILE is supported. In +// that case, the FD is sent over socket. +// ----------------------------------------------------------------------------- +class IPC_EXPORT MessageAttachmentSet + : public base::RefCountedThreadSafe { + public: + MessageAttachmentSet(); + + // Return the number of attachments + unsigned size() const; + + // Return true if no unconsumed descriptors remain + bool empty() const { return attachments_.empty(); } + + // Returns whether the attachment was successfully added. + // |index| is an output variable. On success, it contains the index of the + // newly added attachment. + bool AddAttachment(scoped_refptr attachment, + size_t* index); + + // Similar to the above method, but without output variables. + bool AddAttachment(scoped_refptr attachment); + + // Take the nth from the beginning of the vector, Code using this /must/ + // access the attachments in order, and must do it at most once. + // + // This interface is designed for the deserialising code as it doesn't + // support close flags. + // returns: an attachment, or nullptr on error + scoped_refptr GetAttachmentAt(unsigned index); + + // Marks all the descriptors as consumed and closes those which are + // auto-close. + void CommitAllDescriptors(); + +#if defined(OS_POSIX) + // This is the maximum number of descriptors per message. We need to know this + // because the control message kernel interface has to be given a buffer which + // is large enough to store all the descriptor numbers. Otherwise the kernel + // tells us that it truncated the control data and the extra descriptors are + // lost. + // + // In debugging mode, it's a fatal error to try and add more than this number + // of descriptors to a MessageAttachmentSet. + static const size_t kMaxDescriptorsPerMessage = 7; +#endif // OS_POSIX + + // --------------------------------------------------------------------------- + + private: + friend class base::RefCountedThreadSafe; + + ~MessageAttachmentSet(); + + // Return the number of file descriptors + unsigned num_descriptors() const; + + std::vector> attachments_; + + // This contains the index of the next descriptor which should be consumed. + // It's used in a couple of ways. Firstly, at destruction we can check that + // all the descriptors have been read (with GetNthDescriptor). Secondly, we + // can check that they are read in order. + unsigned consumed_descriptor_highwater_; + + DISALLOW_COPY_AND_ASSIGN(MessageAttachmentSet); +}; + +} // namespace IPC + +#endif // IPC_IPC_MESSAGE_ATTACHMENT_SET_H_ diff --git a/ipc/ipc_message_start.h b/ipc/ipc_message_start.h new file mode 100644 index 0000000000000000000000000000000000000000..13686355effab2dc2bdf8cdc5b81620f8c85e8a2 --- /dev/null +++ b/ipc/ipc_message_start.h @@ -0,0 +1,126 @@ +// Copyright 2012 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 IPC_IPC_MESSAGE_START_H_ +#define IPC_IPC_MESSAGE_START_H_ + +// Used by IPC_BEGIN_MESSAGES so that each message class starts from a unique +// base. Messages have unique IDs across channels in order for the IPC logging +// code to figure out the message class from its ID. +enum IPCMessageStart { + AutomationMsgStart = 0, + FrameMsgStart, + PageMsgStart, + ViewMsgStart, + InputMsgStart, + ProfileImportMsgStart, + TestMsgStart, + DevToolsMsgStart, + WorkerMsgStart, + NaClMsgStart, + UtilityMsgStart, + GpuChannelMsgStart, + GpuMsgStart, + MediaMsgStart, + ServiceMsgStart, + PpapiMsgStart, + FirefoxImporterUnittestMsgStart, + FileUtilitiesMsgStart, + DatabaseMsgStart, + DOMStorageMsgStart, + SpeechRecognitionMsgStart, + SafeBrowsingMsgStart, + P2PMsgStart, + ResourceMsgStart, + FileSystemMsgStart, + ChildProcessMsgStart, + ClipboardMsgStart, + BlobMsgStart, + AppCacheMsgStart, + AudioMsgStart, + MidiMsgStart, + ChromeMsgStart, + DragMsgStart, + PrintMsgStart, + SpellCheckMsgStart, + ExtensionMsgStart, + VideoCaptureMsgStart, + QuotaMsgStart, + TextInputClientMsgStart, + ChromeUtilityMsgStart, + MediaStreamMsgStart, + ChromeBenchmarkingMsgStart, + JavaBridgeMsgStart, + GamepadMsgStart, + ShellMsgStart, + AccessibilityMsgStart, + PrefetchMsgStart, + PrerenderMsgStart, + ChromotingMsgStart, + BrowserPluginMsgStart, + AndroidWebViewMsgStart, + MetroViewerMsgStart, + CCMsgStart, + MediaPlayerMsgStart, + TracingMsgStart, + PeerConnectionTrackerMsgStart, + VisitedLinkMsgStart, + AppShimMsgStart, + WebRtcLoggingMsgStart, + TtsMsgStart, + WebSocketMsgStart, + NaClHostMsgStart, + WebRTCIdentityMsgStart, + PowerMonitorMsgStart, + EncryptedMediaMsgStart, + CacheStorageMsgStart, + ServiceWorkerMsgStart, + MessagePortMsgStart, + EmbeddedWorkerMsgStart, + EmbeddedWorkerContextMsgStart, + CastMsgStart, + CdmMsgStart, + MediaStreamTrackMetricsHostMsgStart, + ChromeExtensionMsgStart, + PushMessagingMsgStart, + GinJavaBridgeMsgStart, + ChromeUtilityPrintingMsgStart, + AecDumpMsgStart, + OzoneGpuMsgStart, + ChromeUtilityExtensionsMsgStart, + PlatformNotificationMsgStart, + PDFMsgStart, + ManifestManagerMsgStart, + LayoutTestMsgStart, + NetworkHintsMsgStart, + BluetoothMsgStart, + CastMediaMsgStart, + AwMessagePortMsgStart, + SyncCompositorMsgStart, + ExtensionsGuestViewMsgStart, + GuestViewMsgStart, + // Note: CastCryptoMsgStart and CastChannelMsgStart reserved for Chromecast + // internal code. Contact gunsch@ before changing/removing. + CastCryptoMsgStart, + CastChannelMsgStart, + DataReductionProxyStart, + ChromeAppBannerMsgStart, + AttachmentBrokerMsgStart, + RenderProcessMsgStart, + PageLoadMetricsMsgStart, + MemoryMsgStart, + IPCTestMsgStart, + ArcInstanceMsgStart, + ArcInstanceHostMsgStart, + DistillerMsgStart, + ArcCameraMsgStart, + DWriteFontProxyMsgStart, + MediaPlayerDelegateMsgStart, + SurfaceViewManagerMsgStart, + ExtensionWorkerMsgStart, + SubresourceFilterMsgStart, + LastIPCMsgStart // Must come last. +}; + +#endif // IPC_IPC_MESSAGE_START_H_ diff --git a/ipc/ipc_message_utils.cc b/ipc/ipc_message_utils.cc new file mode 100644 index 0000000000000000000000000000000000000000..bf8daa5d9741189df50bdac48f7b39cf5744e5a2 --- /dev/null +++ b/ipc/ipc_message_utils.cc @@ -0,0 +1,1238 @@ +// Copyright (c) 2012 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. + +#include "ipc/ipc_message_utils.h" + +#include +#include + +#include "base/files/file_path.h" +#include "base/json/json_writer.h" +#include "base/strings/nullable_string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "base/unguessable_token.h" +#include "base/values.h" +#include "build/build_config.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_message_attachment.h" +#include "ipc/ipc_message_attachment_set.h" +#include "ipc/ipc_mojo_param_traits.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#include "ipc/ipc_platform_file_attachment_posix.h" +#endif + +#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) +#include "base/memory/shared_memory_handle.h" +#endif // (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "ipc/mach_port_mac.h" +#endif + +#if defined(OS_WIN) +#include +#include "ipc/handle_win.h" +#endif + +namespace IPC { + +namespace { + +const int kMaxRecursionDepth = 200; + +template +void LogBytes(const std::vector& data, std::string* out) { +#if defined(OS_WIN) + // Windows has a GUI for logging, which can handle arbitrary binary data. + for (size_t i = 0; i < data.size(); ++i) + out->push_back(data[i]); +#else + // On POSIX, we log to stdout, which we assume can display ASCII. + static const size_t kMaxBytesToLog = 100; + for (size_t i = 0; i < std::min(data.size(), kMaxBytesToLog); ++i) { + if (isprint(data[i])) + out->push_back(data[i]); + else + out->append( + base::StringPrintf("[%02X]", static_cast(data[i]))); + } + if (data.size() > kMaxBytesToLog) { + out->append(base::StringPrintf( + " and %u more bytes", + static_cast(data.size() - kMaxBytesToLog))); + } +#endif +} + +bool ReadValue(const base::Pickle* m, + base::PickleIterator* iter, + base::Value** value, + int recursion); + +void GetValueSize(base::PickleSizer* sizer, + const base::Value* value, + int recursion) { + if (recursion > kMaxRecursionDepth) { + LOG(ERROR) << "Max recursion depth hit in GetValueSize."; + return; + } + + sizer->AddInt(); + switch (value->GetType()) { + case base::Value::Type::NONE: + break; + case base::Value::Type::BOOLEAN: + sizer->AddBool(); + break; + case base::Value::Type::INTEGER: + sizer->AddInt(); + break; + case base::Value::Type::DOUBLE: + sizer->AddDouble(); + break; + case base::Value::Type::STRING: { + const base::Value* result; + value->GetAsString(&result); + if (value->GetAsString(&result)) { + DCHECK(result); + GetParamSize(sizer, result->GetString()); + } else { + std::string str; + bool as_string_result = value->GetAsString(&str); + DCHECK(as_string_result); + GetParamSize(sizer, str); + } + break; + } + case base::Value::Type::BINARY: { + sizer->AddData(static_cast(value->GetSize())); + break; + } + case base::Value::Type::DICTIONARY: { + sizer->AddInt(); + const base::DictionaryValue* dict = + static_cast(value); + for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); + it.Advance()) { + GetParamSize(sizer, it.key()); + GetValueSize(sizer, &it.value(), recursion + 1); + } + break; + } + case base::Value::Type::LIST: { + sizer->AddInt(); + const base::ListValue* list = static_cast(value); + for (const auto& entry : *list) { + GetValueSize(sizer, entry.get(), recursion + 1); + } + break; + } + default: + NOTREACHED() << "Invalid base::Value type."; + } +} + +void WriteValue(base::Pickle* m, const base::Value* value, int recursion) { + bool result; + if (recursion > kMaxRecursionDepth) { + LOG(ERROR) << "Max recursion depth hit in WriteValue."; + return; + } + + m->WriteInt(static_cast(value->GetType())); + + switch (value->GetType()) { + case base::Value::Type::NONE: + break; + case base::Value::Type::BOOLEAN: { + bool val; + result = value->GetAsBoolean(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::Type::INTEGER: { + int val; + result = value->GetAsInteger(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::Type::DOUBLE: { + double val; + result = value->GetAsDouble(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::Type::STRING: { + std::string val; + result = value->GetAsString(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::Type::BINARY: { + m->WriteData(value->GetBuffer(), static_cast(value->GetSize())); + break; + } + case base::Value::Type::DICTIONARY: { + const base::DictionaryValue* dict = + static_cast(value); + + WriteParam(m, static_cast(dict->size())); + + for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); + it.Advance()) { + WriteParam(m, it.key()); + WriteValue(m, &it.value(), recursion + 1); + } + break; + } + case base::Value::Type::LIST: { + const base::ListValue* list = static_cast(value); + WriteParam(m, static_cast(list->GetSize())); + for (const auto& entry : *list) { + WriteValue(m, entry.get(), recursion + 1); + } + break; + } + } +} + +// Helper for ReadValue that reads a DictionaryValue into a pre-allocated +// object. +bool ReadDictionaryValue(const base::Pickle* m, + base::PickleIterator* iter, + base::DictionaryValue* value, + int recursion) { + int size; + if (!ReadParam(m, iter, &size)) + return false; + + for (int i = 0; i < size; ++i) { + std::string key; + base::Value* subval; + if (!ReadParam(m, iter, &key) || + !ReadValue(m, iter, &subval, recursion + 1)) + return false; + value->SetWithoutPathExpansion(key, subval); + } + + return true; +} + +// Helper for ReadValue that reads a ReadListValue into a pre-allocated +// object. +bool ReadListValue(const base::Pickle* m, + base::PickleIterator* iter, + base::ListValue* value, + int recursion) { + int size; + if (!ReadParam(m, iter, &size)) + return false; + + for (int i = 0; i < size; ++i) { + base::Value* subval; + if (!ReadValue(m, iter, &subval, recursion + 1)) + return false; + value->Set(i, subval); + } + + return true; +} + +bool ReadValue(const base::Pickle* m, + base::PickleIterator* iter, + base::Value** value, + int recursion) { + if (recursion > kMaxRecursionDepth) { + LOG(ERROR) << "Max recursion depth hit in ReadValue."; + return false; + } + + int type; + if (!ReadParam(m, iter, &type)) + return false; + + switch (static_cast(type)) { + case base::Value::Type::NONE: + *value = base::Value::CreateNullValue().release(); + break; + case base::Value::Type::BOOLEAN: { + bool val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::Value(val); + break; + } + case base::Value::Type::INTEGER: { + int val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::Value(val); + break; + } + case base::Value::Type::DOUBLE: { + double val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::Value(val); + break; + } + case base::Value::Type::STRING: { + std::string val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::Value(val); + break; + } + case base::Value::Type::BINARY: { + const char* data; + int length; + if (!iter->ReadData(&data, &length)) + return false; + std::unique_ptr val = + base::BinaryValue::CreateWithCopiedBuffer(data, length); + *value = val.release(); + break; + } + case base::Value::Type::DICTIONARY: { + std::unique_ptr val(new base::DictionaryValue()); + if (!ReadDictionaryValue(m, iter, val.get(), recursion)) + return false; + *value = val.release(); + break; + } + case base::Value::Type::LIST: { + std::unique_ptr val(new base::ListValue()); + if (!ReadListValue(m, iter, val.get(), recursion)) + return false; + *value = val.release(); + break; + } + default: + return false; + } + + return true; +} + +} // namespace + +// ----------------------------------------------------------------------------- + +LogData::LogData() + : routing_id(0), + type(0), + sent(0), + receive(0), + dispatch(0) { +} + +LogData::LogData(const LogData& other) = default; + +LogData::~LogData() { +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(p ? "true" : "false"); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddBytes(sizeof(param_type)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteBytes(&p, sizeof(param_type)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char* data; + if (!iter->ReadBytes(&data, sizeof(param_type))) + return false; + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::IntToString(p)); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddBytes(sizeof(param_type)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteBytes(&p, sizeof(param_type)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char* data; + if (!iter->ReadBytes(&data, sizeof(param_type))) + return false; + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::UintToString(p)); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddBytes(sizeof(param_type)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteBytes(&p, sizeof(param_type)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char* data; + if (!iter->ReadBytes(&data, sizeof(param_type))) + return false; + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::UintToString(p)); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::IntToString(p)); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::UintToString(p)); +} + +#if defined(OS_WIN) || defined(OS_LINUX) || \ + (defined(OS_ANDROID) && defined(ARCH_CPU_64_BITS)) +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::Int64ToString(static_cast(p))); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::Uint64ToString(static_cast(p))); +} +#endif + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::Int64ToString(static_cast(p))); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::Uint64ToString(p)); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("%e", p)); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddBytes(sizeof(param_type)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteBytes(reinterpret_cast(&p), sizeof(param_type)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char *data; + if (!iter->ReadBytes(&data, sizeof(*r))) { + NOTREACHED(); + return false; + } + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("%e", p)); +} + + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(p); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::UTF16ToUTF8(p)); +} + +void ParamTraits>::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddData(static_cast(p.size())); +} + +void ParamTraits>::Write(base::Pickle* m, + const param_type& p) { + if (p.empty()) { + m->WriteData(NULL, 0); + } else { + m->WriteData(&p.front(), static_cast(p.size())); + } +} + +bool ParamTraits>::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + if (!iter->ReadData(&data, &data_size) || data_size < 0) + return false; + r->resize(data_size); + if (data_size) + memcpy(&r->front(), data, data_size); + return true; +} + +void ParamTraits >::Log(const param_type& p, std::string* l) { + LogBytes(p, l); +} + +void ParamTraits>::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddData(static_cast(p.size())); +} + +void ParamTraits>::Write(base::Pickle* m, + const param_type& p) { + if (p.empty()) { + m->WriteData(NULL, 0); + } else { + m->WriteData(reinterpret_cast(&p.front()), + static_cast(p.size())); + } +} + +bool ParamTraits>::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + if (!iter->ReadData(&data, &data_size) || data_size < 0) + return false; + r->resize(data_size); + if (data_size) + memcpy(&r->front(), data, data_size); + return true; +} + +void ParamTraits >::Log(const param_type& p, + std::string* l) { + LogBytes(p, l); +} + +void ParamTraits>::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, static_cast(p.size())); + for (size_t i = 0; i < p.size(); ++i) + GetParamSize(sizer, static_cast(p[i])); +} + +void ParamTraits>::Write(base::Pickle* m, + const param_type& p) { + WriteParam(m, static_cast(p.size())); + // Cast to bool below is required because libc++'s + // vector::const_reference is different from bool, and we want to avoid + // writing an extra specialization of ParamTraits for it. + for (size_t i = 0; i < p.size(); i++) + WriteParam(m, static_cast(p[i])); +} + +bool ParamTraits>::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + // ReadLength() checks for < 0 itself. + if (!iter->ReadLength(&size)) + return false; + r->resize(size); + for (int i = 0; i < size; i++) { + bool value; + if (!ReadParam(m, iter, &value)) + return false; + (*r)[i] = value; + } + return true; +} + +void ParamTraits >::Log(const param_type& p, std::string* l) { + for (size_t i = 0; i < p.size(); ++i) { + if (i != 0) + l->push_back(' '); + LogParam(static_cast(p[i]), l); + } +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetValueSize(sizer, &p, 0); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + WriteValue(m, &p, 0); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int type; + if (!ReadParam(m, iter, &type) || + type != static_cast(base::Value::Type::DICTIONARY)) + return false; + + return ReadDictionaryValue(m, iter, r, 0); +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + std::string json; + base::JSONWriter::Write(p, &json); + l->append(json); +} + +#if defined(OS_POSIX) +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.fd >= 0); + if (p.fd >= 0) + sizer->AddAttachment(); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + const bool valid = p.fd >= 0; + WriteParam(m, valid); + + if (!valid) + return; + + if (p.auto_close) { + if (!m->WriteAttachment( + new internal::PlatformFileAttachment(base::ScopedFD(p.fd)))) + NOTREACHED(); + } else { + if (!m->WriteAttachment(new internal::PlatformFileAttachment(p.fd))) + NOTREACHED(); + } +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + *r = base::FileDescriptor(); + + bool valid; + if (!ReadParam(m, iter, &valid)) + return false; + + // TODO(morrita): Seems like this should return false. + if (!valid) + return true; + + scoped_refptr attachment; + if (!m->ReadAttachment(iter, &attachment)) + return false; + + if (static_cast(attachment.get())->GetType() != + MessageAttachment::Type::PLATFORM_FILE) { + return false; + } + + *r = base::FileDescriptor( + static_cast(attachment.get()) + ->TakePlatformFile(), + true); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + if (p.auto_close) { + l->append(base::StringPrintf("FD(%d auto-close)", p.fd)); + } else { + l->append(base::StringPrintf("FD(%d)", p.fd)); + } +} +#endif // defined(OS_POSIX) + +#if defined(OS_MACOSX) && !defined(OS_IOS) +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.GetMemoryObject()); + uint32_t dummy = 0; + GetParamSize(sizer, dummy); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + MachPortMac mach_port_mac(p.GetMemoryObject()); + ParamTraits::Write(m, mach_port_mac); + size_t size = 0; + bool result = p.GetSize(&size); + DCHECK(result); + ParamTraits::Write(m, static_cast(size)); + + // If the caller intended to pass ownership to the IPC stack, release a + // reference. + if (p.OwnershipPassesToIPC()) + p.Close(); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + MachPortMac mach_port_mac; + if (!ParamTraits::Read(m, iter, &mach_port_mac)) + return false; + + uint32_t size; + if (!ParamTraits::Read(m, iter, &size)) + return false; + + *r = base::SharedMemoryHandle(mach_port_mac.get_mach_port(), + static_cast(size), + base::GetCurrentProcId()); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("Mach port: "); + LogParam(p.GetMemoryObject(), l); +} + +#elif defined(OS_WIN) +void ParamTraits::GetSize(base::PickleSizer* s, + const param_type& p) { + GetParamSize(s, p.NeedsBrokering()); + if (p.NeedsBrokering()) { + GetParamSize(s, p.GetHandle()); + } else { + GetParamSize(s, HandleToLong(p.GetHandle())); + } +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + m->WriteBool(p.NeedsBrokering()); + + if (p.NeedsBrokering()) { + HandleWin handle_win(p.GetHandle(), HandleWin::DUPLICATE); + ParamTraits::Write(m, handle_win); + + // If the caller intended to pass ownership to the IPC stack, release a + // reference. + if (p.OwnershipPassesToIPC() && p.BelongsToCurrentProcess()) + p.Close(); + } else { + m->WriteInt(HandleToLong(p.GetHandle())); + } +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + bool needs_brokering; + if (!iter->ReadBool(&needs_brokering)) + return false; + + if (needs_brokering) { + HandleWin handle_win; + if (!ParamTraits::Read(m, iter, &handle_win)) + return false; + *r = base::SharedMemoryHandle(handle_win.get_handle(), + base::GetCurrentProcId()); + return true; + } + + int handle_int; + if (!iter->ReadInt(&handle_int)) + return false; + HANDLE handle = LongToHandle(handle_int); + *r = base::SharedMemoryHandle(handle, base::GetCurrentProcId()); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + LogParam(p.GetHandle(), l); + l->append(" needs brokering: "); + LogParam(p.NeedsBrokering(), l); +} +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + p.GetSizeForPickle(sizer); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + p.WriteToPickle(m); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return r->ReadFromPickle(iter); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + ParamTraits::Log(p.value(), l); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetValueSize(sizer, &p, 0); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + WriteValue(m, &p, 0); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int type; + if (!ReadParam(m, iter, &type) || + type != static_cast(base::Value::Type::LIST)) + return false; + + return ReadListValue(m, iter, r, 0); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + std::string json; + base::JSONWriter::Write(p, &json); + l->append(json); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.string()); + GetParamSize(sizer, p.is_null()); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + WriteParam(m, p.string()); + WriteParam(m, p.is_null()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + base::string16 string; + if (!ReadParam(m, iter, &string)) + return false; + bool is_null; + if (!ReadParam(m, iter, &is_null)) + return false; + *r = base::NullableString16(string, is_null); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("("); + LogParam(p.string(), l); + l->append(", "); + LogParam(p.is_null(), l); + l->append(")"); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.size); + GetParamSize(sizer, p.is_directory); + GetParamSize(sizer, p.last_modified.ToDoubleT()); + GetParamSize(sizer, p.last_accessed.ToDoubleT()); + GetParamSize(sizer, p.creation_time.ToDoubleT()); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + WriteParam(m, p.size); + WriteParam(m, p.is_directory); + WriteParam(m, p.last_modified.ToDoubleT()); + WriteParam(m, p.last_accessed.ToDoubleT()); + WriteParam(m, p.creation_time.ToDoubleT()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* p) { + double last_modified, last_accessed, creation_time; + if (!ReadParam(m, iter, &p->size) || + !ReadParam(m, iter, &p->is_directory) || + !ReadParam(m, iter, &last_modified) || + !ReadParam(m, iter, &last_accessed) || + !ReadParam(m, iter, &creation_time)) + return false; + p->last_modified = base::Time::FromDoubleT(last_modified); + p->last_accessed = base::Time::FromDoubleT(last_accessed); + p->creation_time = base::Time::FromDoubleT(creation_time); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("("); + LogParam(p.size, l); + l->append(","); + LogParam(p.is_directory, l); + l->append(","); + LogParam(p.last_modified.ToDoubleT(), l); + l->append(","); + LogParam(p.last_accessed.ToDoubleT(), l); + l->append(","); + LogParam(p.creation_time.ToDoubleT(), l); + l->append(")"); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt64(); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + ParamTraits::Write(m, p.ToInternalValue()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int64_t value; + if (!ParamTraits::Read(m, iter, &value)) + return false; + *r = base::Time::FromInternalValue(value); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + ParamTraits::Log(p.ToInternalValue(), l); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt64(); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + ParamTraits::Write(m, p.ToInternalValue()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int64_t value; + bool ret = ParamTraits::Read(m, iter, &value); + if (ret) + *r = base::TimeDelta::FromInternalValue(value); + + return ret; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + ParamTraits::Log(p.ToInternalValue(), l); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt64(); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + ParamTraits::Write(m, p.ToInternalValue()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int64_t value; + bool ret = ParamTraits::Read(m, iter, &value); + if (ret) + *r = base::TimeTicks::FromInternalValue(value); + + return ret; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + ParamTraits::Log(p.ToInternalValue(), l); +} + +// If base::UnguessableToken is no longer 128 bits, the IPC serialization logic +// below should be updated. +static_assert(sizeof(base::UnguessableToken) == 2 * sizeof(uint64_t), + "base::UnguessableToken should be of size 2 * sizeof(uint64_t)."); + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddBytes(2 * sizeof(uint64_t)); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + DCHECK(!p.is_empty()); + + ParamTraits::Write(m, p.GetHighForSerialization()); + ParamTraits::Write(m, p.GetLowForSerialization()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + uint64_t high, low; + if (!ParamTraits::Read(m, iter, &high) || + !ParamTraits::Read(m, iter, &low)) + return false; + + // Receiving a zeroed UnguessableToken is a security issue. + if (high == 0 && low == 0) + return false; + + *r = base::UnguessableToken::Deserialize(high, low); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append(p.ToString()); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { +#if defined(OS_NACL_SFI) + GetParamSize(sizer, p.socket); +#else + GetParamSize(sizer, p.mojo_handle); +#endif +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { +#if defined(OS_NACL_SFI) + WriteParam(m, p.socket); +#else + WriteParam(m, p.mojo_handle); +#endif +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { +#if defined(OS_NACL_SFI) + return ReadParam(m, iter, &r->socket); +#else + return ReadParam(m, iter, &r->mojo_handle); +#endif +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("ChannelHandle("); +#if defined(OS_NACL_SFI) + ParamTraits::Log(p.socket, l); +#else + LogParam(p.mojo_handle, l); +#endif + l->append(")"); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.channel); + GetParamSize(sizer, p.routing_id); + GetParamSize(sizer, p.type); + GetParamSize(sizer, p.flags); + GetParamSize(sizer, p.sent); + GetParamSize(sizer, p.receive); + GetParamSize(sizer, p.dispatch); + GetParamSize(sizer, p.message_name); + GetParamSize(sizer, p.params); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + WriteParam(m, p.channel); + WriteParam(m, p.routing_id); + WriteParam(m, p.type); + WriteParam(m, p.flags); + WriteParam(m, p.sent); + WriteParam(m, p.receive); + WriteParam(m, p.dispatch); + WriteParam(m, p.message_name); + WriteParam(m, p.params); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return + ReadParam(m, iter, &r->channel) && + ReadParam(m, iter, &r->routing_id) && + ReadParam(m, iter, &r->type) && + ReadParam(m, iter, &r->flags) && + ReadParam(m, iter, &r->sent) && + ReadParam(m, iter, &r->receive) && + ReadParam(m, iter, &r->dispatch) && + ReadParam(m, iter, &r->message_name) && + ReadParam(m, iter, &r->params); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + // Doesn't make sense to implement this! +} + +void ParamTraits::Write(base::Pickle* m, const Message& p) { +#if defined(OS_POSIX) + // We don't serialize the file descriptors in the nested message, so there + // better not be any. + DCHECK(!p.HasAttachments()); +#endif + + // Don't just write out the message. This is used to send messages between + // NaCl (Posix environment) and the browser (could be on Windows). The message + // header formats differ between these systems (so does handle sharing, but + // we already asserted we don't have any handles). So just write out the + // parts of the header we use. + // + // Be careful also to use only explicitly-sized types. The NaCl environment + // could be 64-bit and the host browser could be 32-bits. The nested message + // may or may not be safe to send between 32-bit and 64-bit systems, but we + // leave that up to the code sending the message to ensure. + m->WriteUInt32(static_cast(p.routing_id())); + m->WriteUInt32(p.type()); + m->WriteUInt32(p.flags()); + m->WriteData(p.payload(), static_cast(p.payload_size())); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + Message* r) { + uint32_t routing_id, type, flags; + if (!iter->ReadUInt32(&routing_id) || + !iter->ReadUInt32(&type) || + !iter->ReadUInt32(&flags)) + return false; + + int payload_size; + const char* payload; + if (!iter->ReadData(&payload, &payload_size)) + return false; + + r->SetHeaderValues(static_cast(routing_id), type, flags); + return r->WriteBytes(payload, payload_size); +} + +void ParamTraits::Log(const Message& p, std::string* l) { + l->append(""); +} + +#if defined(OS_WIN) +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt(); +} + +// Note that HWNDs/HANDLE/HCURSOR/HACCEL etc are always 32 bits, even on 64 +// bit systems. That's why we use the Windows macros to convert to 32 bits. +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteInt(HandleToLong(p)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int32_t temp; + if (!iter->ReadInt(&temp)) + return false; + *r = LongToHandle(temp); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("0x%p", p)); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddData(sizeof(LOGFONT)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteData(reinterpret_cast(&p), sizeof(LOGFONT)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + if (iter->ReadData(&data, &data_size) && data_size == sizeof(LOGFONT)) { + const LOGFONT *font = reinterpret_cast(const_cast(data)); + if (_tcsnlen(font->lfFaceName, LF_FACESIZE) < LF_FACESIZE) { + memcpy(r, data, sizeof(LOGFONT)); + return true; + } + } + + NOTREACHED(); + return false; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("")); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddData(sizeof(MSG)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteData(reinterpret_cast(&p), sizeof(MSG)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + bool result = iter->ReadData(&data, &data_size); + if (result && data_size == sizeof(MSG)) { + memcpy(r, data, sizeof(MSG)); + } else { + result = false; + NOTREACHED(); + } + + return result; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(""); +} + +#endif // OS_WIN + +} // namespace IPC diff --git a/ipc/ipc_message_utils.h b/ipc/ipc_message_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..2d51c984aa0c0fdf005b355c989bfb6774b72bf0 --- /dev/null +++ b/ipc/ipc_message_utils.h @@ -0,0 +1,1145 @@ +// Copyright (c) 2012 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 IPC_IPC_MESSAGE_UTILS_H_ +#define IPC_IPC_MESSAGE_UTILS_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "base/containers/small_map.h" +#include "base/containers/stack_container.h" +#include "base/files/file.h" +#include "base/format_macros.h" +#include "base/optional.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "ipc/ipc_message_start.h" +#include "ipc/ipc_param_traits.h" +#include "ipc/ipc_sync_message.h" + +namespace base { +class DictionaryValue; +class FilePath; +class ListValue; +class NullableString16; +class Time; +class TimeDelta; +class TimeTicks; +class UnguessableToken; +struct FileDescriptor; + +#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) +class SharedMemoryHandle; +#endif // (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) +} + +namespace IPC { + +struct ChannelHandle; + +// ----------------------------------------------------------------------------- +// How we send IPC message logs across channels. +struct IPC_EXPORT LogData { + LogData(); + LogData(const LogData& other); + ~LogData(); + + std::string channel; + int32_t routing_id; + uint32_t type; // "User-defined" message type, from ipc_message.h. + std::string flags; + int64_t sent; // Time that the message was sent (i.e. at Send()). + int64_t receive; // Time before it was dispatched (i.e. before calling + // OnMessageReceived). + int64_t dispatch; // Time after it was dispatched (i.e. after calling + // OnMessageReceived). + std::string message_name; + std::string params; +}; + +//----------------------------------------------------------------------------- + +// A dummy struct to place first just to allow leading commas for all +// members in the macro-generated constructor initializer lists. +struct NoParams { +}; + +// Specializations are checked by 'IPC checker' part of find-bad-constructs +// Clang plugin (see WriteParam() below for the details). +template +struct CheckedTuple { + typedef std::tuple Tuple; +}; + +template +static inline void GetParamSize(base::PickleSizer* sizer, const P& p) { + typedef typename SimilarTypeTraits

::Type Type; + ParamTraits::GetSize(sizer, static_cast(p)); +} + +// This function is checked by 'IPC checker' part of find-bad-constructs +// Clang plugin to make it's not called on the following types: +// 1. long / unsigned long (but not typedefs to) +// 2. intmax_t, uintmax_t, intptr_t, uintptr_t, wint_t, +// size_t, rsize_t, ssize_t, ptrdiff_t, dev_t, off_t, clock_t, +// time_t, suseconds_t (including typedefs to) +// 3. Any template referencing types above (e.g. std::vector) +template +static inline void WriteParam(base::Pickle* m, const P& p) { + typedef typename SimilarTypeTraits

::Type Type; + ParamTraits::Write(m, static_cast(p)); +} + +template +static inline bool WARN_UNUSED_RESULT ReadParam(const base::Pickle* m, + base::PickleIterator* iter, + P* p) { + typedef typename SimilarTypeTraits

::Type Type; + return ParamTraits::Read(m, iter, reinterpret_cast(p)); +} + +template +static inline void LogParam(const P& p, std::string* l) { + typedef typename SimilarTypeTraits

::Type Type; + ParamTraits::Log(static_cast(p), l); +} + +// Primitive ParamTraits ------------------------------------------------------- + +template <> +struct ParamTraits { + typedef bool param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddBool(); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteBool(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadBool(r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef signed char param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef unsigned char param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef unsigned short param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits { + typedef int param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddInt(); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteInt(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadInt(r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits { + typedef unsigned int param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddInt(); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteInt(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadInt(reinterpret_cast(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +// long isn't safe to send over IPC because it's 4 bytes on 32 bit builds but +// 8 bytes on 64 bit builds. So if a 32 bit and 64 bit process have a channel +// that would cause problem. +// We need to keep this on for a few configs: +// 1) Windows because DWORD is typedef'd to it, which is fine because we have +// very few IPCs that cross this boundary. +// 2) We also need to keep it for Linux for two reasons: int64_t is typedef'd +// to long, and gfx::PluginWindow is long and is used in one GPU IPC. +// 3) Android 64 bit also has int64_t typedef'd to long. +// Since we want to support Android 32<>64 bit IPC, as long as we don't have +// these traits for 32 bit ARM then that'll catch any errors. +#if defined(OS_WIN) || defined(OS_LINUX) || \ + (defined(OS_ANDROID) && defined(ARCH_CPU_64_BITS)) +template <> +struct ParamTraits { + typedef long param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddLong(); + } + static void Write(base::Pickle* m, const param_type& p) { + m->WriteLong(p); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadLong(r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits { + typedef unsigned long param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddLong(); + } + static void Write(base::Pickle* m, const param_type& p) { + m->WriteLong(p); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadLong(reinterpret_cast(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; +#endif + +template <> +struct ParamTraits { + typedef long long param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddInt64(); + } + static void Write(base::Pickle* m, const param_type& p) { + m->WriteInt64(static_cast(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadInt64(reinterpret_cast(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits { + typedef unsigned long long param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddInt64(); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteInt64(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadInt64(reinterpret_cast(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +// Note that the IPC layer doesn't sanitize NaNs and +/- INF values. Clients +// should be sure to check the sanity of these values after receiving them over +// IPC. +template <> +struct IPC_EXPORT ParamTraits { + typedef float param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddFloat(); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteFloat(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadFloat(r); + } + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef double param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template +struct ParamTraits { + using param_type = P[Size]; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + for (const P& element : p) + GetParamSize(sizer, element); + } + static void Write(base::Pickle* m, const param_type& p) { + for (const P& element : p) + WriteParam(m, element); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + for (P& element : *r) { + if (!ReadParam(m, iter, &element)) + return false; + } + return true; + } + static void Log(const param_type& p, std::string* l) { + l->append("["); + for (const P& element : p) { + if (&element != &p[0]) + l->append(" "); + LogParam(element, l); + } + l->append("]"); + } +}; + +// STL ParamTraits ------------------------------------------------------------- + +template <> +struct ParamTraits { + typedef std::string param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddString(p); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteString(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadString(r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits { + typedef base::string16 param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddString16(p); + } + static void Write(base::Pickle* m, const param_type& p) { + m->WriteString16(p); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadString16(r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits > { + typedef std::vector param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle*, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits > { + typedef std::vector param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits > { + typedef std::vector param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template +struct ParamTraits> { + typedef std::vector

param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, static_cast(p.size())); + for (size_t i = 0; i < p.size(); i++) + GetParamSize(sizer, p[i]); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, static_cast(p.size())); + for (size_t i = 0; i < p.size(); i++) + WriteParam(m, p[i]); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + // ReadLength() checks for < 0 itself. + if (!iter->ReadLength(&size)) + return false; + // Resizing beforehand is not safe, see BUG 1006367 for details. + if (INT_MAX / sizeof(P) <= static_cast(size)) + return false; + r->resize(size); + for (int i = 0; i < size; i++) { + if (!ReadParam(m, iter, &(*r)[i])) + return false; + } + return true; + } + static void Log(const param_type& p, std::string* l) { + for (size_t i = 0; i < p.size(); ++i) { + if (i != 0) + l->append(" "); + LogParam((p[i]), l); + } + } +}; + +template +struct ParamTraits > { + typedef std::set

param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) + GetParamSize(sizer, *iter); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) + WriteParam(m, *iter); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + if (!iter->ReadLength(&size)) + return false; + for (int i = 0; i < size; ++i) { + P item; + if (!ReadParam(m, iter, &item)) + return false; + r->insert(item); + } + return true; + } + static void Log(const param_type& p, std::string* l) { + l->append(""); + } +}; + +template +struct ParamTraits > { + typedef std::map param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) { + GetParamSize(sizer, iter->first); + GetParamSize(sizer, iter->second); + } + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) { + WriteParam(m, iter->first); + WriteParam(m, iter->second); + } + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + if (!ReadParam(m, iter, &size) || size < 0) + return false; + for (int i = 0; i < size; ++i) { + K k; + if (!ReadParam(m, iter, &k)) + return false; + V& value = (*r)[k]; + if (!ReadParam(m, iter, &value)) + return false; + } + return true; + } + static void Log(const param_type& p, std::string* l) { + l->append(""); + } +}; + +template +struct ParamTraits > { + typedef std::pair param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, p.first); + GetParamSize(sizer, p.second); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, p.first); + WriteParam(m, p.second); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return ReadParam(m, iter, &r->first) && ReadParam(m, iter, &r->second); + } + static void Log(const param_type& p, std::string* l) { + l->append("("); + LogParam(p.first, l); + l->append(", "); + LogParam(p.second, l); + l->append(")"); + } +}; + +// Base ParamTraits ------------------------------------------------------------ + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::DictionaryValue param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +#if defined(OS_POSIX) +// FileDescriptors may be serialised over IPC channels on POSIX. On the +// receiving side, the FileDescriptor is a valid duplicate of the file +// descriptor which was transmitted: *it is not just a copy of the integer like +// HANDLEs on Windows*. The only exception is if the file descriptor is < 0. In +// this case, the receiving end will see a value of -1. *Zero is a valid file +// descriptor*. +// +// The received file descriptor will have the |auto_close| flag set to true. The +// code which handles the message is responsible for taking ownership of it. +// File descriptors are OS resources and must be closed when no longer needed. +// +// When sending a file descriptor, the file descriptor must be valid at the time +// of transmission. Since transmission is not synchronous, one should consider +// dup()ing any file descriptors to be transmitted and setting the |auto_close| +// flag, which causes the file descriptor to be closed after writing. +template<> +struct IPC_EXPORT ParamTraits { + typedef base::FileDescriptor param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; +#endif // defined(OS_POSIX) + +#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) +template <> +struct IPC_EXPORT ParamTraits { + typedef base::SharedMemoryHandle param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; +#endif // (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::FilePath param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::ListValue param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::NullableString16 param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::File::Info param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct SimilarTypeTraits { + typedef int Type; +}; + +#if defined(OS_WIN) +template <> +struct SimilarTypeTraits { + typedef HANDLE Type; +}; +#endif // defined(OS_WIN) + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::Time param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::TimeDelta param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::TimeTicks param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::UnguessableToken param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits> { + typedef std::tuple<> param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) {} + static void Write(base::Pickle* m, const param_type& p) {} + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return true; + } + static void Log(const param_type& p, std::string* l) { + } +}; + +template +struct ParamTraits> { + typedef std::tuple param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, std::get<0>(p)); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, std::get<0>(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return ReadParam(m, iter, &std::get<0>(*r)); + } + static void Log(const param_type& p, std::string* l) { + LogParam(std::get<0>(p), l); + } +}; + +template +struct ParamTraits> { + typedef std::tuple param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, std::get<0>(p)); + GetParamSize(sizer, std::get<1>(p)); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, std::get<0>(p)); + WriteParam(m, std::get<1>(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return (ReadParam(m, iter, &std::get<0>(*r)) && + ReadParam(m, iter, &std::get<1>(*r))); + } + static void Log(const param_type& p, std::string* l) { + LogParam(std::get<0>(p), l); + l->append(", "); + LogParam(std::get<1>(p), l); + } +}; + +template +struct ParamTraits> { + typedef std::tuple param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, std::get<0>(p)); + GetParamSize(sizer, std::get<1>(p)); + GetParamSize(sizer, std::get<2>(p)); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, std::get<0>(p)); + WriteParam(m, std::get<1>(p)); + WriteParam(m, std::get<2>(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return (ReadParam(m, iter, &std::get<0>(*r)) && + ReadParam(m, iter, &std::get<1>(*r)) && + ReadParam(m, iter, &std::get<2>(*r))); + } + static void Log(const param_type& p, std::string* l) { + LogParam(std::get<0>(p), l); + l->append(", "); + LogParam(std::get<1>(p), l); + l->append(", "); + LogParam(std::get<2>(p), l); + } +}; + +template +struct ParamTraits> { + typedef std::tuple param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, std::get<0>(p)); + GetParamSize(sizer, std::get<1>(p)); + GetParamSize(sizer, std::get<2>(p)); + GetParamSize(sizer, std::get<3>(p)); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, std::get<0>(p)); + WriteParam(m, std::get<1>(p)); + WriteParam(m, std::get<2>(p)); + WriteParam(m, std::get<3>(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return (ReadParam(m, iter, &std::get<0>(*r)) && + ReadParam(m, iter, &std::get<1>(*r)) && + ReadParam(m, iter, &std::get<2>(*r)) && + ReadParam(m, iter, &std::get<3>(*r))); + } + static void Log(const param_type& p, std::string* l) { + LogParam(std::get<0>(p), l); + l->append(", "); + LogParam(std::get<1>(p), l); + l->append(", "); + LogParam(std::get<2>(p), l); + l->append(", "); + LogParam(std::get<3>(p), l); + } +}; + +template +struct ParamTraits> { + typedef std::tuple param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, std::get<0>(p)); + GetParamSize(sizer, std::get<1>(p)); + GetParamSize(sizer, std::get<2>(p)); + GetParamSize(sizer, std::get<3>(p)); + GetParamSize(sizer, std::get<4>(p)); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, std::get<0>(p)); + WriteParam(m, std::get<1>(p)); + WriteParam(m, std::get<2>(p)); + WriteParam(m, std::get<3>(p)); + WriteParam(m, std::get<4>(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return (ReadParam(m, iter, &std::get<0>(*r)) && + ReadParam(m, iter, &std::get<1>(*r)) && + ReadParam(m, iter, &std::get<2>(*r)) && + ReadParam(m, iter, &std::get<3>(*r)) && + ReadParam(m, iter, &std::get<4>(*r))); + } + static void Log(const param_type& p, std::string* l) { + LogParam(std::get<0>(p), l); + l->append(", "); + LogParam(std::get<1>(p), l); + l->append(", "); + LogParam(std::get<2>(p), l); + l->append(", "); + LogParam(std::get<3>(p), l); + l->append(", "); + LogParam(std::get<4>(p), l); + } +}; + +template +struct ParamTraits > { + typedef base::StackVector param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, static_cast(p->size())); + for (size_t i = 0; i < p->size(); i++) + GetParamSize(sizer, p[i]); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, static_cast(p->size())); + for (size_t i = 0; i < p->size(); i++) + WriteParam(m, p[i]); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + // ReadLength() checks for < 0 itself. + if (!iter->ReadLength(&size)) + return false; + // Sanity check for the vector size. + if (INT_MAX / sizeof(P) <= static_cast(size)) + return false; + P value; + for (int i = 0; i < size; i++) { + if (!ReadParam(m, iter, &value)) + return false; + (*r)->push_back(value); + } + return true; + } + static void Log(const param_type& p, std::string* l) { + for (size_t i = 0; i < p->size(); ++i) { + if (i != 0) + l->append(" "); + LogParam((p[i]), l); + } + } +}; + +template +struct ParamTraits > { + typedef base::SmallMap param_type; + typedef typename param_type::key_type K; + typedef typename param_type::data_type V; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) { + GetParamSize(sizer, iter->first); + GetParamSize(sizer, iter->second); + } + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) { + WriteParam(m, iter->first); + WriteParam(m, iter->second); + } + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + if (!iter->ReadLength(&size)) + return false; + for (int i = 0; i < size; ++i) { + K key; + if (!ReadParam(m, iter, &key)) + return false; + V& value = (*r)[key]; + if (!ReadParam(m, iter, &value)) + return false; + } + return true; + } + static void Log(const param_type& p, std::string* l) { + l->append(""); + } +}; + +template +struct ParamTraits> { + typedef std::unique_ptr

param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + bool valid = !!p; + GetParamSize(sizer, valid); + if (valid) + GetParamSize(sizer, *p); + } + static void Write(base::Pickle* m, const param_type& p) { + bool valid = !!p; + WriteParam(m, valid); + if (valid) + WriteParam(m, *p); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + bool valid = false; + if (!ReadParam(m, iter, &valid)) + return false; + + if (!valid) { + r->reset(); + return true; + } + + param_type temp(new P()); + if (!ReadParam(m, iter, temp.get())) + return false; + + r->swap(temp); + return true; + } + static void Log(const param_type& p, std::string* l) { + if (p) + LogParam(*p, l); + else + l->append("NULL"); + } +}; + +template +struct ParamTraits> { + typedef base::Optional

param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + const bool is_set = static_cast(p); + GetParamSize(sizer, is_set); + if (is_set) + GetParamSize(sizer, p.value()); + } + static void Write(base::Pickle* m, const param_type& p) { + const bool is_set = static_cast(p); + WriteParam(m, is_set); + if (is_set) + WriteParam(m, p.value()); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + bool is_set = false; + if (!iter->ReadBool(&is_set)) + return false; + if (is_set) { + P value; + if (!ReadParam(m, iter, &value)) + return false; + *r = std::move(value); + } + return true; + } + static void Log(const param_type& p, std::string* l) { + if (p) + LogParam(p.value(), l); + else + l->append("(unset)"); + } +}; + +// IPC types ParamTraits ------------------------------------------------------- + +// A ChannelHandle is basically a platform-inspecific wrapper around the +// fact that IPC endpoints are handled specially on POSIX. See above comments +// on FileDescriptor for more background. +template<> +struct IPC_EXPORT ParamTraits { + typedef ChannelHandle param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef LogData param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + static void Write(base::Pickle* m, const Message& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + Message* r); + static void Log(const Message& p, std::string* l); +}; + +// Windows ParamTraits --------------------------------------------------------- + +#if defined(OS_WIN) +template <> +struct IPC_EXPORT ParamTraits { + typedef HANDLE param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef LOGFONT param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef MSG param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; +#endif // defined(OS_WIN) + +//----------------------------------------------------------------------------- +// Generic message subclasses + +// defined in ipc_logging.cc +IPC_EXPORT void GenerateLogData(const Message& message, + LogData* data, + bool get_params); + +#if defined(IPC_MESSAGE_LOG_ENABLED) +inline void AddOutputParamsToLog(const Message* msg, std::string* l) { + const std::string& output_params = msg->output_params(); + if (!l->empty() && !output_params.empty()) + l->append(", "); + + l->append(output_params); +} + +template +inline void LogReplyParamsToMessage(const ReplyParamType& reply_params, + const Message* msg) { + if (msg->received_time() != 0) { + std::string output_params; + LogParam(reply_params, &output_params); + msg->set_output_params(output_params); + } +} + +inline void ConnectMessageAndReply(const Message* msg, Message* reply) { + if (msg->sent_time()) { + // Don't log the sync message after dispatch, as we don't have the + // output parameters at that point. Instead, save its data and log it + // with the outgoing reply message when it's sent. + LogData* data = new LogData; + GenerateLogData(*msg, data, true); + msg->set_dont_log(); + reply->set_sync_log_data(data); + } +} +#else +inline void AddOutputParamsToLog(const Message* msg, std::string* l) {} + +template +inline void LogReplyParamsToMessage(const ReplyParamType& reply_params, + const Message* msg) {} + +inline void ConnectMessageAndReply(const Message* msg, Message* reply) {} +#endif + +} // namespace IPC + +#endif // IPC_IPC_MESSAGE_UTILS_H_ diff --git a/ipc/ipc_mojo_handle_attachment.cc b/ipc/ipc_mojo_handle_attachment.cc new file mode 100644 index 0000000000000000000000000000000000000000..e3421c3e88bc31aa78b4d0b51427cb4948bb5d36 --- /dev/null +++ b/ipc/ipc_mojo_handle_attachment.cc @@ -0,0 +1,29 @@ +// Copyright (c) 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. + +#include "ipc/ipc_mojo_handle_attachment.h" + +#include + +#include "build/build_config.h" + +namespace IPC { +namespace internal { + +MojoHandleAttachment::MojoHandleAttachment(mojo::ScopedHandle handle) + : handle_(std::move(handle)) {} + +MojoHandleAttachment::~MojoHandleAttachment() { +} + +MessageAttachment::Type MojoHandleAttachment::GetType() const { + return Type::MOJO_HANDLE; +} + +mojo::ScopedHandle MojoHandleAttachment::TakeHandle() { + return std::move(handle_); +} + +} // namespace internal +} // namespace IPC diff --git a/ipc/ipc_mojo_handle_attachment.h b/ipc/ipc_mojo_handle_attachment.h new file mode 100644 index 0000000000000000000000000000000000000000..d6152769efd59e8d7300515684c4546f9f6022f7 --- /dev/null +++ b/ipc/ipc_mojo_handle_attachment.h @@ -0,0 +1,42 @@ +// Copyright (c) 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. + +#ifndef IPC_IPC_MOJO_HANDLE_ATTACHMENT_H_ +#define IPC_IPC_MOJO_HANDLE_ATTACHMENT_H_ + +#include "base/files/file.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "ipc/ipc_export.h" +#include "ipc/ipc_message_attachment.h" +#include "mojo/public/cpp/system/handle.h" + +namespace IPC { + +namespace internal { + +// A MessageAttachment that holds a MojoHandle. +// This can hold any type of transferrable Mojo handle (i.e. message pipe, data +// pipe, etc), but the receiver is expected to know what type of handle to +// expect. +class IPC_EXPORT MojoHandleAttachment : public MessageAttachment { + public: + explicit MojoHandleAttachment(mojo::ScopedHandle handle); + + Type GetType() const override; + + // Returns the owning handle transferring the ownership. + mojo::ScopedHandle TakeHandle(); + + private: + ~MojoHandleAttachment() override; + mojo::ScopedHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(MojoHandleAttachment); +}; + +} // namespace internal +} // namespace IPC + +#endif // IPC_IPC_MOJO_HANDLE_ATTACHMENT_H_ diff --git a/ipc/ipc_mojo_message_helper.cc b/ipc/ipc_mojo_message_helper.cc new file mode 100644 index 0000000000000000000000000000000000000000..a87a2d694d5f974f10461f0eeedea771dc159567 --- /dev/null +++ b/ipc/ipc_mojo_message_helper.cc @@ -0,0 +1,51 @@ +// Copyright (c) 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. + +#include "ipc/ipc_mojo_message_helper.h" + +#include + +#include "ipc/ipc_mojo_handle_attachment.h" + +namespace IPC { + +// static +bool MojoMessageHelper::WriteMessagePipeTo( + base::Pickle* message, + mojo::ScopedMessagePipeHandle handle) { + message->WriteAttachment(new internal::MojoHandleAttachment( + mojo::ScopedHandle::From(std::move(handle)))); + return true; +} + +// static +bool MojoMessageHelper::ReadMessagePipeFrom( + const base::Pickle* message, + base::PickleIterator* iter, + mojo::ScopedMessagePipeHandle* handle) { + scoped_refptr attachment; + if (!message->ReadAttachment(iter, &attachment)) { + LOG(ERROR) << "Failed to read attachment for message pipe."; + return false; + } + + MessageAttachment::Type type = + static_cast(attachment.get())->GetType(); + if (type != MessageAttachment::Type::MOJO_HANDLE) { + LOG(ERROR) << "Unxpected attachment type:" << type; + return false; + } + + handle->reset(mojo::MessagePipeHandle( + static_cast(attachment.get()) + ->TakeHandle() + .release() + .value())); + return true; +} + +MojoMessageHelper::MojoMessageHelper() { +} + +} // namespace IPC diff --git a/ipc/ipc_mojo_message_helper.h b/ipc/ipc_mojo_message_helper.h new file mode 100644 index 0000000000000000000000000000000000000000..4a71b5c96cd4c53332a5be17586c305e02f362d5 --- /dev/null +++ b/ipc/ipc_mojo_message_helper.h @@ -0,0 +1,29 @@ +// Copyright (c) 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. + +#ifndef IPC_IPC_MOJO_MESSAGE_HELPER_H_ +#define IPC_IPC_MOJO_MESSAGE_HELPER_H_ + +#include "ipc/ipc_export.h" +#include "ipc/ipc_message.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace IPC { + +// Reads and writes |mojo::MessagePipe| from/to |Message|. +class IPC_EXPORT MojoMessageHelper { + public: + static bool WriteMessagePipeTo(base::Pickle* message, + mojo::ScopedMessagePipeHandle handle); + static bool ReadMessagePipeFrom(const base::Pickle* message, + base::PickleIterator* iter, + mojo::ScopedMessagePipeHandle* handle); + + private: + MojoMessageHelper(); +}; + +} // namespace IPC + +#endif // IPC_IPC_MOJO_MESSAGE_HELPER_H_ diff --git a/ipc/ipc_mojo_param_traits.cc b/ipc/ipc_mojo_param_traits.cc new file mode 100644 index 0000000000000000000000000000000000000000..189af3511d35260a17b0e18e9d191385931b539c --- /dev/null +++ b/ipc/ipc_mojo_param_traits.cc @@ -0,0 +1,50 @@ +// 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. + +#include "ipc/ipc_mojo_param_traits.h" + +#include "ipc/ipc_message_utils.h" +#include "ipc/ipc_mojo_message_helper.h" + +namespace IPC { + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.is_valid()); + if (p.is_valid()) + sizer->AddAttachment(); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + WriteParam(m, p.is_valid()); + if (p.is_valid()) + MojoMessageHelper::WriteMessagePipeTo(m, mojo::ScopedMessagePipeHandle(p)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + bool is_valid; + if (!ReadParam(m, iter, &is_valid)) + return false; + if (!is_valid) + return true; + + mojo::ScopedMessagePipeHandle handle; + if (!MojoMessageHelper::ReadMessagePipeFrom(m, iter, &handle)) + return false; + DCHECK(handle.is_valid()); + *r = handle.release(); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("mojo::MessagePipeHandle("); + LogParam(p.value(), l); + l->append(")"); +} + +} // namespace IPC diff --git a/ipc/ipc_mojo_param_traits.h b/ipc/ipc_mojo_param_traits.h new file mode 100644 index 0000000000000000000000000000000000000000..39be43e217a300d337f0cd7d9e9fcb96041ae54f --- /dev/null +++ b/ipc/ipc_mojo_param_traits.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef IPC_IPC_MOJO_PARAM_TRAITS_H_ +#define IPC_IPC_MOJO_PARAM_TRAITS_H_ + +#include + +#include "ipc/ipc_export.h" +#include "ipc/ipc_param_traits.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace base { +class Pickle; +class PickleIterator; +class PickleSizer; +} + +namespace IPC { + +template <> +struct IPC_EXPORT ParamTraits { + typedef mojo::MessagePipeHandle param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +} // namespace IPC + +#endif // IPC_IPC_MOJO_PARAM_TRAITS_H_ diff --git a/ipc/ipc_param_traits.h b/ipc/ipc_param_traits.h new file mode 100644 index 0000000000000000000000000000000000000000..45e975c30a746aa7fdad5fc47713b03f479d8fb8 --- /dev/null +++ b/ipc/ipc_param_traits.h @@ -0,0 +1,23 @@ +// Copyright (c) 2010 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 IPC_IPC_PARAM_TRAITS_H_ +#define IPC_IPC_PARAM_TRAITS_H_ + +// Our IPC system uses the following partially specialized header to define how +// a data type is read, written and logged in the IPC system. + +namespace IPC { + +template struct ParamTraits { +}; + +template +struct SimilarTypeTraits { + typedef P Type; +}; + +} // namespace IPC + +#endif // IPC_IPC_PARAM_TRAITS_H_ diff --git a/ipc/ipc_platform_file_attachment_posix.cc b/ipc/ipc_platform_file_attachment_posix.cc new file mode 100644 index 0000000000000000000000000000000000000000..7111cfa0bbb4979d7ff3eb67848c94ccc925baf5 --- /dev/null +++ b/ipc/ipc_platform_file_attachment_posix.cc @@ -0,0 +1,38 @@ +// Copyright (c) 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. + +#include "ipc/ipc_platform_file_attachment_posix.h" + +#include + +namespace IPC { +namespace internal { + +PlatformFileAttachment::PlatformFileAttachment(base::PlatformFile file) + : file_(file) { +} + +PlatformFileAttachment::PlatformFileAttachment(base::ScopedFD file) + : file_(file.get()), owning_(std::move(file)) {} + +PlatformFileAttachment::~PlatformFileAttachment() { +} + +MessageAttachment::Type PlatformFileAttachment::GetType() const { + return Type::PLATFORM_FILE; +} + +base::PlatformFile PlatformFileAttachment::TakePlatformFile() { + ignore_result(owning_.release()); + return file_; +} + +base::PlatformFile GetPlatformFile( + scoped_refptr attachment) { + DCHECK_EQ(attachment->GetType(), MessageAttachment::Type::PLATFORM_FILE); + return static_cast(attachment.get())->file(); +} + +} // namespace internal +} // namespace IPC diff --git a/ipc/ipc_platform_file_attachment_posix.h b/ipc/ipc_platform_file_attachment_posix.h new file mode 100644 index 0000000000000000000000000000000000000000..9b0790093b0608adc83fff9dce981e94955af572 --- /dev/null +++ b/ipc/ipc_platform_file_attachment_posix.h @@ -0,0 +1,43 @@ +// Copyright (c) 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. + +#ifndef IPC_IPC_PLATFORM_FILE_ATTACHMENT_H_ +#define IPC_IPC_PLATFORM_FILE_ATTACHMENT_H_ + +#include "ipc/ipc_export.h" +#include "ipc/ipc_message_attachment.h" + +namespace IPC { +namespace internal { + +// A platform file that is sent over |Channel| as a part of |Message|. +// PlatformFileAttachment optionally owns the file and |owning_| is set in that +// case. Also, |file_| is not cleared even after the ownership is taken. +// Some old clients require this strange behavior. +class IPC_EXPORT PlatformFileAttachment : public MessageAttachment { + public: + // Non-owning constructor + explicit PlatformFileAttachment(base::PlatformFile file); + // Owning constructor + explicit PlatformFileAttachment(base::ScopedFD file); + + Type GetType() const override; + base::PlatformFile TakePlatformFile(); + + base::PlatformFile file() const { return file_; } + bool Owns() const { return owning_.is_valid(); } + + private: + ~PlatformFileAttachment() override; + + base::PlatformFile file_; + base::ScopedFD owning_; +}; + +base::PlatformFile GetPlatformFile(scoped_refptr attachment); + +} // namespace internal +} // namespace IPC + +#endif // IPC_IPC_PLATFORM_FILE_ATTACHMENT_H_ diff --git a/ipc/ipc_sync_message.h b/ipc/ipc_sync_message.h new file mode 100644 index 0000000000000000000000000000000000000000..7f0555134a222e8e88e44871cd70ed3c65e423bf --- /dev/null +++ b/ipc/ipc_sync_message.h @@ -0,0 +1,107 @@ +// Copyright (c) 2012 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 IPC_IPC_SYNC_MESSAGE_H_ +#define IPC_IPC_SYNC_MESSAGE_H_ + +#include + +#if defined(OS_WIN) +#include +#endif + +#include +#include + +#include "build/build_config.h" +#include "ipc/ipc_message.h" + +namespace base { +class WaitableEvent; +} + +namespace IPC { + +class MessageReplyDeserializer; + +class IPC_EXPORT SyncMessage : public Message { + public: + SyncMessage(int32_t routing_id, + uint32_t type, + PriorityValue priority, + MessageReplyDeserializer* deserializer); + ~SyncMessage() override; + + // Call this to get a deserializer for the output parameters. + // Note that this can only be called once, and the caller is responsible + // for deleting the deserializer when they're done. + MessageReplyDeserializer* GetReplyDeserializer(); + + // If this message can cause the receiver to block while waiting for user + // input (i.e. by calling MessageBox), then the caller needs to pump window + // messages and dispatch asynchronous messages while waiting for the reply. + // This call enables message pumping behavior while waiting for a reply to + // this message. + void EnableMessagePumping() { + header()->flags |= PUMPING_MSGS_BIT; + } + + // Indicates whether window messages should be pumped while waiting for a + // reply to this message. + bool ShouldPumpMessages() const { + return (header()->flags & PUMPING_MSGS_BIT) != 0; + } + + // Returns true if the message is a reply to the given request id. + static bool IsMessageReplyTo(const Message& msg, int request_id); + + // Given a reply message, returns an iterator to the beginning of the data + // (i.e. skips over the synchronous specific data). + static base::PickleIterator GetDataIterator(const Message* msg); + + // Given a synchronous message (or its reply), returns its id. + static int GetMessageId(const Message& msg); + + // Generates a reply message to the given message. + static Message* GenerateReply(const Message* msg); + + private: + struct SyncHeader { + // unique ID (unique per sender) + int message_id; + }; + + static bool ReadSyncHeader(const Message& msg, SyncHeader* header); + static bool WriteSyncHeader(Message* msg, const SyncHeader& header); + + std::unique_ptr deserializer_; +}; + +// Used to deserialize parameters from a reply to a synchronous message +class IPC_EXPORT MessageReplyDeserializer { + public: + virtual ~MessageReplyDeserializer() {} + bool SerializeOutputParameters(const Message& msg); + private: + // Derived classes need to implement this, using the given iterator (which + // is skipped past the header for synchronous messages). + virtual bool SerializeOutputParameters(const Message& msg, + base::PickleIterator iter) = 0; +}; + +// When sending a synchronous message, this structure contains an object +// that knows how to deserialize the response. +struct PendingSyncMsg { + PendingSyncMsg(int id, MessageReplyDeserializer* d, base::WaitableEvent* e) + : id(id), deserializer(d), done_event(e), send_result(false) {} + + int id; + MessageReplyDeserializer* deserializer; + base::WaitableEvent* done_event; + bool send_result; +}; + +} // namespace IPC + +#endif // IPC_IPC_SYNC_MESSAGE_H_ diff --git a/libchrome_tools/jni_generator_helper.sh b/libchrome_tools/jni_generator_helper.sh new file mode 100755 index 0000000000000000000000000000000000000000..d610801e91d790c37b8e370b228d21bde5a6e8d7 --- /dev/null +++ b/libchrome_tools/jni_generator_helper.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Generates jni. + +set -e + +args=() +files=() + +jni_generator='' + +for arg in "$@"; do + case "${arg}" in + --jni_generator=*) + jni_generator=${arg#'--jni_generator='} + ;; + --*) + args=("${args[@]}" "${arg}") + ;; + *) + files=("${files[@]}" "${arg}") + ;; + esac +done + +for file in "${files[@]}"; do + "${jni_generator}" "${args[@]}" --input_file="${file}" +done diff --git a/libchrome_tools/mojom_generate_type_mappings.py b/libchrome_tools/mojom_generate_type_mappings.py new file mode 100644 index 0000000000000000000000000000000000000000..0c8023c40f941d364aae899835b2e73766e7a76a --- /dev/null +++ b/libchrome_tools/mojom_generate_type_mappings.py @@ -0,0 +1,93 @@ +#!/usr/bin/python + +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Drives mojom typemapping generator. + +Usage: + +% python libchrome_tools/mojom_generate_type_mappings.py \ + --output ${output_type_mapping_file_path} \ + ${list of .typemap files} +""" + +import argparse +import os +import subprocess +import sys + +from build import gn_helpers + +_GENERATE_TYPE_MAPPINGS_PATH = os.path.join( + os.path.dirname(__file__), + '../mojo/public/tools/bindings/generate_type_mappings.py') + +def _read_typemap_config(path): + """Reads .typemap file. + + Args: + path: File path to the .typemap location. + + Returns: + A dictionary holding values in .typemap file. + """ + + with open(path) as f: + # gn_helpers does not handle comment lines. + content = [line for line in f if not line.strip().startswith('#')] + return gn_helpers.FromGNArgs(''.join(content)) + + +def _generate_type_mappings(input_paths, output): + """Generates __type_mappings file from given .typemap files. + + Builds a command line to run generate_type_mappings.py, and executes it. + + Args: + input_paths: a list of file paths for .typemap files. + output: a path to output __type_mappings file. + """ + command = [sys.executable, _GENERATE_TYPE_MAPPINGS_PATH, '--output', output] + + # TODO(hidehiko): Add dependency handling. + + for path in input_paths: + typemap_config = _read_typemap_config(path) + command.append('--start-typemap') + for public_header in typemap_config.get('public_headers', []): + command.append('public_headers=' + public_header) + for traits_header in typemap_config.get('traits_headers', []): + command.append('traits_headers=' + traits_header) + for type_mapping in typemap_config.get('type_mappings', []): + command.append('type_mappings=' + type_mapping) + + subprocess.check_call(command) + + +def _parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('--output', help='Output file path') + parser.add_argument('input_paths', metavar="INPUT-PATH", nargs='+', + help='Input typemap files.') + return parser.parse_args() + + +def main(): + args = _parse_args() + _generate_type_mappings(args.input_paths, args.output) + + +if __name__ == '__main__': + main() diff --git a/libchrome_tools/mojom_source_generator.sh b/libchrome_tools/mojom_source_generator.sh new file mode 100755 index 0000000000000000000000000000000000000000..a60658feee5b5472c4c5bf7347ff5ab3323424af --- /dev/null +++ b/libchrome_tools/mojom_source_generator.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Generates mojo sources given a list of .mojom files and args. +# Usage: $0 --mojom_bindings_generator= --package= +# --output_dir= +# [] + +set -e + +args=() +files=() + +mojom_bindings_generator="" +package="" +output_dir="" +generators="" +private_mojo_root="$(pwd)/external/libchrome" + +# Given a path to directory or file, return the absolute path. +get_abs_path() { + if [[ -d $1 ]] ; then + cd "$1" + filename="" + else + filepath=$1 + dir="${filepath%/*}" + cd "${dir}" + filename="${filepath#${dir}/}" + fi + absdir=`pwd` + cd - > /dev/null + echo "${absdir}/${filename}" +} + +for arg in "$@"; do + case "${arg}" in + --mojom_bindings_generator=*) + mojom_bindings_generator="${arg#'--mojom_bindings_generator='}" + mojom_bindings_generator="$(get_abs_path ${mojom_bindings_generator})" + ;; + --package=*) + package="${arg#'--package='}" + ;; + --output_dir=*) + output_dir="${arg#'--output_dir='}" + output_dir="$(get_abs_path ${output_dir})" + ;; + --typemap=*) + typemap="${arg#'--typemap='}" + typemap="$(get_abs_path ${typemap})" + args=("${args[@]}" "--typemap=${typemap}") + ;; + --bytecode_path=*) + bytecode_path="${arg#'--bytecode_path='}" + bytecode_path="$(get_abs_path ${bytecode_path})" + ;; + --generators=*) + generators="${arg#'--generators='}" + ;; + --srcjar=*) + srcjar="${arg#'--srcjar='}" + srcjar="$(get_abs_path ${srcjar})" + ;; + --*) + args=("${args[@]}" "${arg}") + ;; + *) + files=("${files[@]}" "$(get_abs_path ${arg})") + ;; + esac +done + +cd "${package}" +"${mojom_bindings_generator}" --use_bundled_pylibs precompile \ + -o "${output_dir}" + +for file in "${files[@]}"; do + # Java source generations depends on zipfile that assumes the output directory + # already exists. So, we need to create the directory beforehand. + rel_path="${file#`pwd`/}" + rel_dir="${rel_path%/*}" + + mkdir -p "${output_dir}/${rel_dir}" + + # The calls to mojom_bindings_generator below uses -I option to include the + # libmojo root directory as part of searchable directory for imports. With + # this, we can have a mojo file located in some arbitrary directories that + # imports a mojo file under external/libchrome. + "${mojom_bindings_generator}" --use_bundled_pylibs generate \ + -o "${output_dir}" "${args[@]}" \ + --bytecode_path="${bytecode_path}" \ + -I "${private_mojo_root}:${private_mojo_root}" \ + --generators=${generators} "${file}" + if [[ "${generators}" =~ .*c\+\+.* ]] ; then + "${mojom_bindings_generator}" --use_bundled_pylibs generate \ + -o "${output_dir}" \ + --generate_non_variant_code "${args[@]}" \ + -I "${private_mojo_root}:${private_mojo_root}" \ + --bytecode_path="${bytecode_path}" --generators=${generators} \ + "${file}" + fi + if [[ "${generators}" =~ .*java.* ]] ; then + unzip -qo -d "${output_dir}"/src "${output_dir}/${rel_path}".srcjar + fi +done + +if [[ -n "${srcjar}" ]] ; then + (cd "${output_dir}/src" && \ + find . -name '*.java' -print | zip --quiet "${srcjar}" -@) +fi diff --git a/libchrome_tools/patch/allocator_shim.patch b/libchrome_tools/patch/allocator_shim.patch new file mode 100644 index 0000000000000000000000000000000000000000..e87f614ecedca205169ba93b08756ee2f3aba0f9 --- /dev/null +++ b/libchrome_tools/patch/allocator_shim.patch @@ -0,0 +1,13 @@ +# Use allocator_shim_override_linker_wrapped_symbols.h for ANDROID. + +--- a/base/allocator/allocator_shim.cc ++++ b/base/allocator/allocator_shim.cc +@@ -299,7 +299,7 @@ ALWAYS_INLINE void ShimFreeDefiniteSize( + #include "base/allocator/allocator_shim_override_cpp_symbols.h" + #endif + +-#if defined(OS_ANDROID) ++#if defined(OS_ANDROID) || defined(ANDROID) + // Android does not support symbol interposition. The way malloc symbols are + // intercepted on Android is by using link-time -wrap flags. + #include "base/allocator/allocator_shim_override_linker_wrapped_symbols.h" diff --git a/libchrome_tools/patch/ashmem.patch b/libchrome_tools/patch/ashmem.patch new file mode 100644 index 0000000000000000000000000000000000000000..c59a5367748d36d5d551b38558cce635a183285b --- /dev/null +++ b/libchrome_tools/patch/ashmem.patch @@ -0,0 +1,20 @@ +--- /dev/null ++++ b/third_party/ashmem/ashmem.h +@@ -0,0 +1,17 @@ ++// Copyright (C) 2018 The Android Open Source Project ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++ ++// third_party/ashmem is Android shared memory. Instead of clone it here, ++// use cutils/ashmem.h directly. ++#include diff --git a/libchrome_tools/patch/build_config.patch b/libchrome_tools/patch/build_config.patch new file mode 100644 index 0000000000000000000000000000000000000000..daf5fb27eff125cd963d0816c4a0b8b66c000c75 --- /dev/null +++ b/libchrome_tools/patch/build_config.patch @@ -0,0 +1,53 @@ +--- a/build/build_config.h ++++ b/build/build_config.h +@@ -16,6 +16,40 @@ + #ifndef BUILD_BUILD_CONFIG_H_ + #define BUILD_BUILD_CONFIG_H_ + ++// A brief primer on #defines: ++// ++// - __ANDROID__ is automatically defined by the Android toolchain (see ++// https://goo.gl/v61lXa). It's not defined when building host code. ++// - __ANDROID_HOST__ is defined via -D by Android.mk when building host code ++// within an Android checkout. ++// - ANDROID is defined via -D when building code for either Android targets or ++// hosts. Use __ANDROID__ and __ANDROID_HOST__ instead. ++// - OS_ANDROID is a Chrome-specific define used to build Chrome for Android ++// within the NDK. ++ ++// Android targets and hosts don't use tcmalloc. ++#if defined(__ANDROID__) || defined(__ANDROID_HOST__) ++#define NO_TCMALLOC ++#endif // defined(__ANDROID__) || defined(__ANDROID_HOST__) ++ ++// Use the Chrome OS version of the code for both Android targets and Chrome OS builds. ++#if !defined(__ANDROID_HOST__) ++#define OS_CHROMEOS 1 ++#endif // !defined(__ANDROID_HOST__) ++ ++#if defined(__ANDROID__) // Android targets ++ ++#define __linux__ 1 ++ ++#elif !defined(__ANDROID_HOST__) // Chrome OS ++ ++// TODO: Remove these once the GLib MessageLoopForUI isn't being used: ++// https://crbug.com/361635 ++#define USE_GLIB 1 ++#define USE_OZONE 1 ++ ++#endif // defined(__ANDROID__) ++ + // A set of macros to use for platform detection. + #if defined(__native_client__) + // __native_client__ must be first, so that other OS_ defines are not set. +@@ -28,8 +62,7 @@ + #else + #define OS_NACL_SFI + #endif +-#elif defined(ANDROID) +-#define OS_ANDROID 1 ++// Don't set OS_ANDROID; it's only used when building Chrome for Android. + #elif defined(__APPLE__) + // only include TargetConditions after testing ANDROID as some android builds + // on mac don't have this header available and it's not needed unless the target diff --git a/libchrome_tools/patch/build_time.patch b/libchrome_tools/patch/build_time.patch new file mode 100644 index 0000000000000000000000000000000000000000..cebe8a6f1d174a3e23e8450f6b0fe5a882f1a3cf --- /dev/null +++ b/libchrome_tools/patch/build_time.patch @@ -0,0 +1,74 @@ +--- a/base/build_time.cc ++++ b/base/build_time.cc +@@ -4,20 +4,31 @@ + + #include "base/build_time.h" + +-// Imports the generated build date, i.e. BUILD_DATE. +-#include "base/generated_build_date.h" +- + #include "base/logging.h" + #include "base/time/time.h" + ++#ifdef __ANDROID__ ++#include ++#endif ++ + namespace base { + + Time GetBuildTime() { + Time integral_build_time; +- // BUILD_DATE is exactly "Mmm DD YYYY HH:MM:SS". +- // See //build/write_build_date_header.py. "HH:MM:SS" is normally expected to +- // be "05:00:00" but is not enforced here. +- bool result = Time::FromUTCString(BUILD_DATE, &integral_build_time); ++ // The format of __DATE__ and __TIME__ is specified by the ANSI C Standard, ++ // section 6.8.8. ++ // ++ // __DATE__ is exactly "Mmm DD YYYY". ++ // __TIME__ is exactly "hh:mm:ss". ++#if defined(__ANDROID__) ++ char kDateTime[PROPERTY_VALUE_MAX]; ++ property_get("ro.build.date", kDateTime, "Sep 02 2008 08:00:00 PST"); ++#elif defined(DONT_EMBED_BUILD_METADATA) && !defined(OFFICIAL_BUILD) ++ const char kDateTime[] = "Sep 02 2008 08:00:00 PST"; ++#else ++ const char kDateTime[] = __DATE__ " " __TIME__ " PST"; ++#endif ++ bool result = Time::FromString(kDateTime, &integral_build_time); + DCHECK(result); + return integral_build_time; + } +--- a/base/build_time_unittest.cc ++++ b/base/build_time_unittest.cc +@@ -3,13 +3,19 @@ + // found in the LICENSE file. + + #include "base/build_time.h" ++#if !defined(DONT_EMBED_BUILD_METADATA) + #include "base/generated_build_date.h" ++#endif + #include "base/time/time.h" + + #include "testing/gtest/include/gtest/gtest.h" + + TEST(BuildTime, DateLooksValid) { ++#if !defined(DONT_EMBED_BUILD_METADATA) + char build_date[] = BUILD_DATE; ++#else ++ char build_date[] = "Sep 02 2008 05:00:00"; ++#endif + + EXPECT_EQ(20u, strlen(build_date)); + EXPECT_EQ(' ', build_date[3]); +@@ -30,8 +36,10 @@ TEST(BuildTime, InThePast) { + EXPECT_LT(base::GetBuildTime(), base::Time::NowFromSystemTime()); + } + ++#if !defined(DONT_EMBED_BUILD_METADATA) + TEST(BuildTime, NotTooFar) { + // BuildTime must be less than 45 days old. + base::Time cutoff(base::Time::Now() - base::TimeDelta::FromDays(45)); + EXPECT_GT(base::GetBuildTime(), cutoff); + } ++#endif diff --git a/libchrome_tools/patch/buildflag_header.patch b/libchrome_tools/patch/buildflag_header.patch new file mode 100644 index 0000000000000000000000000000000000000000..69cc417b7da953a7c7ac43f49fe3664be2f0f4d7 --- /dev/null +++ b/libchrome_tools/patch/buildflag_header.patch @@ -0,0 +1,32 @@ +# These files are generated by buildflag_header rule in Chromium. +# Instead, in libchrome, these are checked in. + +--- /dev/null ++++ b/base/allocator/features.h +@@ -0,0 +1,15 @@ ++// Generated by build/write_buildflag_header.py ++// From "allocator_features" ++ ++#ifndef BASE_ALLOCATOR_FEATURES_H_ ++#define BASE_ALLOCATOR_FEATURES_H_ ++ ++#include "build/buildflag.h" ++ ++#if defined(__APPLE__) || defined(ANDROID) ++#define BUILDFLAG_INTERNAL_USE_EXPERIMENTAL_ALLOCATOR_SHIM() (0) ++#else ++#define BUILDFLAG_INTERNAL_USE_EXPERIMENTAL_ALLOCATOR_SHIM() (1) ++#endif ++ ++#endif // BASE_ALLOCATOR_FEATURES_H_ +--- /dev/null ++++ b/base/debug/debugging_flags.h +@@ -0,0 +1,8 @@ ++// Generated by build/write_buildflag_header.py ++// From "base_debugging_flags" ++#ifndef BASE_DEBUG_DEBUGGING_FLAGS_H_ ++#define BASE_DEBUG_DEBUGGING_FLAGS_H_ ++#include "build/buildflag.h" ++#define BUILDFLAG_INTERNAL_ENABLE_PROFILING() (0) ++#define BUILDFLAG_INTERNAL_ENABLE_MEMORY_TASK_PROFILER() (0) ++#endif // BASE_DEBUG_DEBUGGING_FLAGS_H_ diff --git a/libchrome_tools/patch/compiler_specific.patch b/libchrome_tools/patch/compiler_specific.patch new file mode 100644 index 0000000000000000000000000000000000000000..87d7354c07f6dabdee01c49526355899402f7644 --- /dev/null +++ b/libchrome_tools/patch/compiler_specific.patch @@ -0,0 +1,16 @@ +# In Android, prefer Android's libbase definitions for LIKELY/UNLIKELY macros. + +--- a/base/compiler_specific.h ++++ b/base/compiler_specific.h +@@ -7,6 +7,11 @@ + + #include "build/build_config.h" + ++#if defined(ANDROID) ++// Prefer Android's libbase definitions to our own. ++#include ++#endif // defined(ANDROID) ++ + #if defined(COMPILER_MSVC) + + // For _Printf_format_string_. diff --git a/libchrome_tools/patch/dmg_fp.patch b/libchrome_tools/patch/dmg_fp.patch new file mode 100644 index 0000000000000000000000000000000000000000..50427716c0e71d58b0c30869af2fc5653df46cba --- /dev/null +++ b/libchrome_tools/patch/dmg_fp.patch @@ -0,0 +1,156 @@ +# Libchrome does not support/require dmg_fp library. Instead, use standard +# library. + +--- a/base/strings/string_number_conversions.cc ++++ b/base/strings/string_number_conversions.cc +@@ -15,7 +15,6 @@ + #include "base/logging.h" + #include "base/numerics/safe_math.h" + #include "base/scoped_clear_errno.h" +-#include "base/third_party/dmg_fp/dmg_fp.h" + + namespace base { + +@@ -369,10 +368,18 @@ string16 SizeTToString16(size_t value) { + } + + std::string DoubleToString(double value) { +- // According to g_fmt.cc, it is sufficient to declare a buffer of size 32. +- char buffer[32]; +- dmg_fp::g_fmt(buffer, value); +- return std::string(buffer); ++ auto ret = std::to_string(value); ++ // If this returned an integer, don't do anything. ++ if (ret.find('.') == std::string::npos) { ++ return ret; ++ } ++ // Otherwise, it has an annoying tendency to leave trailing zeros. ++ size_t len = ret.size(); ++ while (len >= 2 && ret[len - 1] == '0' && ret[len - 2] != '.') { ++ --len; ++ } ++ ret.erase(len); ++ return ret; + } + + bool StringToInt(const StringPiece& input, int* output) { +@@ -416,14 +423,10 @@ bool StringToSizeT(const StringPiece16& + } + + bool StringToDouble(const std::string& input, double* output) { +- // Thread-safe? It is on at least Mac, Linux, and Windows. +- ScopedClearErrno clear_errno; +- +- char* endptr = NULL; +- *output = dmg_fp::strtod(input.c_str(), &endptr); ++ char* endptr = nullptr; ++ *output = strtod(input.c_str(), &endptr); + + // Cases to return false: +- // - If errno is ERANGE, there was an overflow or underflow. + // - If the input string is empty, there was nothing to parse. + // - If endptr does not point to the end of the string, there are either + // characters remaining in the string after a parsed number, or the string +@@ -431,10 +434,11 @@ bool StringToDouble(const std::string& i + // expected end given the string's stated length to correctly catch cases + // where the string contains embedded NUL characters. + // - If the first character is a space, there was leading whitespace +- return errno == 0 && +- !input.empty() && ++ return !input.empty() && + input.c_str() + input.length() == endptr && +- !isspace(input[0]); ++ !isspace(input[0]) && ++ *output != std::numeric_limits::infinity() && ++ *output != -std::numeric_limits::infinity(); + } + + // Note: if you need to add String16ToDouble, first ask yourself if it's +--- a/base/strings/string_number_conversions.h ++++ b/base/strings/string_number_conversions.h +@@ -54,6 +54,7 @@ BASE_EXPORT string16 Uint64ToString16(ui + BASE_EXPORT std::string SizeTToString(size_t value); + BASE_EXPORT string16 SizeTToString16(size_t value); + ++// Deprecated: prefer std::to_string(double) instead. + // DoubleToString converts the double to a string format that ignores the + // locale. If you want to use locale specific formatting, use ICU. + BASE_EXPORT std::string DoubleToString(double value); +@@ -91,6 +92,7 @@ BASE_EXPORT bool StringToUint64(const St + BASE_EXPORT bool StringToSizeT(const StringPiece& input, size_t* output); + BASE_EXPORT bool StringToSizeT(const StringPiece16& input, size_t* output); + ++// Deprecated: prefer std::stod() instead. + // For floating-point conversions, only conversions of input strings in decimal + // form are defined to work. Behavior with strings representing floating-point + // numbers in hexadecimal, and strings representing non-finite values (such as +--- a/base/strings/string_number_conversions_unittest.cc ++++ b/base/strings/string_number_conversions_unittest.cc +@@ -752,20 +752,8 @@ TEST(StringNumberConversionsTest, String + {"9e999", HUGE_VAL, false}, + {"9e1999", HUGE_VAL, false}, + {"9e19999", HUGE_VAL, false}, +- {"9e99999999999999999999", HUGE_VAL, false}, +- {"-9e307", -9e307, true}, +- {"-1.7976e308", -1.7976e308, true}, +- {"-1.7977e308", -HUGE_VAL, false}, +- {"-1.797693134862315807e+308", -HUGE_VAL, true}, +- {"-1.797693134862315808e+308", -HUGE_VAL, false}, +- {"-9e308", -HUGE_VAL, false}, +- {"-9e309", -HUGE_VAL, false}, +- {"-9e999", -HUGE_VAL, false}, +- {"-9e1999", -HUGE_VAL, false}, +- {"-9e19999", -HUGE_VAL, false}, +- {"-9e99999999999999999999", -HUGE_VAL, false}, +- +- // Test more exponents. ++ {"9e99999999999999999999", std::numeric_limits::infinity(), false}, ++ {"-9e99999999999999999999", -std::numeric_limits::infinity(), false}, + {"1e-2", 0.01, true}, + {"42 ", 42.0, false}, + {" 1e-2", 0.01, false}, +@@ -795,7 +783,8 @@ TEST(StringNumberConversionsTest, String + for (size_t i = 0; i < arraysize(cases); ++i) { + double output; + errno = 1; +- EXPECT_EQ(cases[i].success, StringToDouble(cases[i].input, &output)); ++ EXPECT_EQ(cases[i].success, StringToDouble(cases[i].input, &output)) ++ << "for input=" << cases[i].input << "got output=" << output; + if (cases[i].success) + EXPECT_EQ(1, errno) << i; // confirm that errno is unchanged. + EXPECT_DOUBLE_EQ(cases[i].output, output); +@@ -816,13 +805,13 @@ TEST(StringNumberConversionsTest, Double + double input; + const char* expected; + } cases[] = { +- {0.0, "0"}, ++ {0.0, "0.0"}, + {1.25, "1.25"}, +- {1.33518e+012, "1.33518e+12"}, +- {1.33489e+012, "1.33489e+12"}, +- {1.33505e+012, "1.33505e+12"}, +- {1.33545e+009, "1335450000"}, +- {1.33503e+009, "1335030000"}, ++ {1.33518e+012, "1335180000000.0"}, ++ {1.33489e+012, "1334890000000.0"}, ++ {1.33505e+012, "1335050000000.0"}, ++ {1.33545e+009, "1335450000.0"}, ++ {1.33503e+009, "1335030000.0"}, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { +@@ -833,12 +822,12 @@ TEST(StringNumberConversionsTest, Double + const char input_bytes[8] = {0, 0, 0, 0, '\xee', '\x6d', '\x73', '\x42'}; + double input = 0; + memcpy(&input, input_bytes, arraysize(input_bytes)); +- EXPECT_EQ("1335179083776", DoubleToString(input)); ++ EXPECT_EQ("1335179083776.0", DoubleToString(input)); + const char input_bytes2[8] = + {0, 0, 0, '\xa0', '\xda', '\x6c', '\x73', '\x42'}; + input = 0; + memcpy(&input, input_bytes2, arraysize(input_bytes2)); +- EXPECT_EQ("1334890332160", DoubleToString(input)); ++ EXPECT_EQ("1334890332160.0", DoubleToString(input)); + } + + TEST(StringNumberConversionsTest, HexEncode) { diff --git a/libchrome_tools/patch/file_posix.patch b/libchrome_tools/patch/file_posix.patch new file mode 100644 index 0000000000000000000000000000000000000000..c7428e347a776cc556013282c547665ca7a336d6 --- /dev/null +++ b/libchrome_tools/patch/file_posix.patch @@ -0,0 +1,15 @@ +# On Android, lseek64 should be used, whlie lseek should be in other platfrom. + +--- a/base/files/file_posix.cc ++++ b/base/files/file_posix.cc +@@ -185,7 +185,9 @@ int64_t File::Seek(Whence whence, int64_ + + SCOPED_FILE_TRACE_WITH_SIZE("Seek", offset); + +-#if defined(OS_ANDROID) ++// Additionally check __BIONIC__ since older versions of Android don't define ++// _FILE_OFFSET_BITS. ++#if _FILE_OFFSET_BITS != 64 || defined(__BIONIC__) + static_assert(sizeof(int64_t) == sizeof(off64_t), "off64_t must be 64 bits"); + return lseek64(file_.get(), static_cast(offset), + static_cast(whence)); diff --git a/libchrome_tools/patch/gmock.patch b/libchrome_tools/patch/gmock.patch new file mode 100644 index 0000000000000000000000000000000000000000..a38afdce257870acd4005cfe328728d91d400675 --- /dev/null +++ b/libchrome_tools/patch/gmock.patch @@ -0,0 +1,6 @@ +# Use system installed gmock library. + +--- /dev/null ++++ b/testing/gmock/include/gmock/gmock.h +@@ -0,0 +1 @@ ++#include diff --git a/libchrome_tools/patch/gtest.patch b/libchrome_tools/patch/gtest.patch new file mode 100644 index 0000000000000000000000000000000000000000..6da17c92e363a6311fcf66252b7c4535be81132f --- /dev/null +++ b/libchrome_tools/patch/gtest.patch @@ -0,0 +1,10 @@ +# Use system installed gtest library. + +--- /dev/null ++++ b/testing/gtest/include/gtest/gtest.h +@@ -0,0 +1 @@ ++#include +--- /dev/null ++++ b/testing/gtest/include/gtest/gtest_prod.h +@@ -0,0 +1 @@ ++#include diff --git a/libchrome_tools/patch/hash.patch b/libchrome_tools/patch/hash.patch new file mode 100644 index 0000000000000000000000000000000000000000..71e13d70e2cbf5f9bccf00f11d4000c76a11b270 --- /dev/null +++ b/libchrome_tools/patch/hash.patch @@ -0,0 +1,28 @@ +# libchrome does not support SuperFastHash. Instead use std::hash. + +--- a/base/hash.cc ++++ b/base/hash.cc +@@ -4,19 +4,13 @@ + + #include "base/hash.h" + +-// Definition in base/third_party/superfasthash/superfasthash.c. (Third-party +-// code did not come with its own header file, so declaring the function here.) +-// Note: This algorithm is also in Blink under Source/wtf/StringHasher.h. +-extern "C" uint32_t SuperFastHash(const char* data, int len); ++#include + + namespace base { + +-uint32_t SuperFastHash(const char* data, size_t length) { +- if (length > static_cast(std::numeric_limits::max())) { +- NOTREACHED(); +- return 0; +- } +- return ::SuperFastHash(data, static_cast(length)); ++uint32_t SuperFastHash(const char* data, size_t len) { ++ std::hash hash_fn; ++ return hash_fn(std::string(data, len)); + } + + } // namespace base diff --git a/libchrome_tools/patch/lazy_instance.patch b/libchrome_tools/patch/lazy_instance.patch new file mode 100644 index 0000000000000000000000000000000000000000..d144137a67818dd2531492ad1f5695638b8a5973 --- /dev/null +++ b/libchrome_tools/patch/lazy_instance.patch @@ -0,0 +1,18 @@ +# LAZY_INSTANCE_INITIALIZER will be embedded into the users of libchrome, +# and could cause compile warning. + +--- a/base/lazy_instance.h ++++ b/base/lazy_instance.h +@@ -48,7 +48,11 @@ + // initialization, as base's LINKER_INITIALIZED requires a constructor and on + // some compilers (notably gcc 4.4) this still ends up needing runtime + // initialization. +-#define LAZY_INSTANCE_INITIALIZER {0} ++#ifdef __clang__ ++ #define LAZY_INSTANCE_INITIALIZER {} ++#else ++ #define LAZY_INSTANCE_INITIALIZER {0, 0} ++#endif + + namespace base { + diff --git a/libchrome_tools/patch/libevent.patch b/libchrome_tools/patch/libevent.patch new file mode 100644 index 0000000000000000000000000000000000000000..723d621654af08e44b75b4f288fdad0bff47bebf --- /dev/null +++ b/libchrome_tools/patch/libevent.patch @@ -0,0 +1,13 @@ +--- /dev/null ++++ b/base/third_party/libevent/event.h +@@ -0,0 +1,10 @@ ++// The Chromium build contains its own checkout of libevent. This stub is used ++// when building the Chrome OS or Android libchrome package to instead use the ++// system headers. ++#if defined(__ANDROID__) || defined(__ANDROID_HOST__) ++#include ++#include ++#include ++#else ++#include ++#endif diff --git a/libchrome_tools/patch/logging.patch b/libchrome_tools/patch/logging.patch new file mode 100644 index 0000000000000000000000000000000000000000..8deece9b862c360cfdd4daf6d7003d6289303743 --- /dev/null +++ b/libchrome_tools/patch/logging.patch @@ -0,0 +1,80 @@ +--- a/base/logging.cc ++++ b/base/logging.cc +@@ -71,7 +71,11 @@ typedef pthread_mutex_t* MutexHandle; + #include "base/posix/safe_strerror.h" + #endif + +-#if defined(OS_ANDROID) ++#if !defined(OS_ANDROID) ++#include "base/files/file_path.h" ++#endif ++ ++#if defined(OS_ANDROID) || defined(__ANDROID__) + #include + #endif + +@@ -358,21 +362,23 @@ bool BaseInitLoggingImpl(const LoggingSe + // Can log only to the system debug log. + CHECK_EQ(settings.logging_dest & ~LOG_TO_SYSTEM_DEBUG_LOG, 0); + #endif +- base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); +- // Don't bother initializing |g_vlog_info| unless we use one of the +- // vlog switches. +- if (command_line->HasSwitch(switches::kV) || +- command_line->HasSwitch(switches::kVModule)) { +- // NOTE: If |g_vlog_info| has already been initialized, it might be in use +- // by another thread. Don't delete the old VLogInfo, just create a second +- // one. We keep track of both to avoid memory leak warnings. +- CHECK(!g_vlog_info_prev); +- g_vlog_info_prev = g_vlog_info; +- +- g_vlog_info = +- new VlogInfo(command_line->GetSwitchValueASCII(switches::kV), +- command_line->GetSwitchValueASCII(switches::kVModule), +- &g_min_log_level); ++ if (base::CommandLine::InitializedForCurrentProcess()) { ++ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); ++ // Don't bother initializing |g_vlog_info| unless we use one of the ++ // vlog switches. ++ if (command_line->HasSwitch(switches::kV) || ++ command_line->HasSwitch(switches::kVModule)) { ++ // NOTE: If |g_vlog_info| has already been initialized, it might be in use ++ // by another thread. Don't delete the old VLogInfo, just create a second ++ // one. We keep track of both to avoid memory leak warnings. ++ CHECK(!g_vlog_info_prev); ++ g_vlog_info_prev = g_vlog_info; ++ ++ g_vlog_info = ++ new VlogInfo(command_line->GetSwitchValueASCII(switches::kV), ++ command_line->GetSwitchValueASCII(switches::kVModule), ++ &g_min_log_level); ++ } + } + + g_logging_destination = settings.logging_dest; +@@ -668,7 +674,7 @@ LogMessage::~LogMessage() { + + asl_send(asl_client.get(), asl_message.get()); + } +-#elif defined(OS_ANDROID) ++#elif defined(OS_ANDROID) || defined(__ANDROID__) + android_LogPriority priority = + (severity_ < 0) ? ANDROID_LOG_VERBOSE : ANDROID_LOG_UNKNOWN; + switch (severity_) { +@@ -685,7 +691,16 @@ LogMessage::~LogMessage() { + priority = ANDROID_LOG_FATAL; + break; + } ++#if defined(OS_ANDROID) + __android_log_write(priority, "chromium", str_newline.c_str()); ++#else ++ __android_log_write( ++ priority, ++ base::CommandLine::InitializedForCurrentProcess() ? ++ base::CommandLine::ForCurrentProcess()-> ++ GetProgram().BaseName().value().c_str() : nullptr, ++ str_newline.c_str()); ++#endif // defined(OS_ANDROID) + #endif + ignore_result(fwrite(str_newline.data(), str_newline.size(), 1, stderr)); + fflush(stderr); diff --git a/libchrome_tools/patch/macros.patch b/libchrome_tools/patch/macros.patch new file mode 100644 index 0000000000000000000000000000000000000000..f57ec8867d382ab033aa27a6d661744720ed5f0e --- /dev/null +++ b/libchrome_tools/patch/macros.patch @@ -0,0 +1,69 @@ +--- a/base/macros.h ++++ b/base/macros.h +@@ -12,19 +12,32 @@ + + #include // For size_t. + ++#if defined(ANDROID) ++// Prefer Android's libbase definitions to our own. ++#include ++#endif // defined(ANDROID) ++ ++// We define following macros conditionally as they may be defined by another libraries. ++ + // Put this in the declarations for a class to be uncopyable. ++#if !defined(DISALLOW_COPY) + #define DISALLOW_COPY(TypeName) \ + TypeName(const TypeName&) = delete ++#endif + + // Put this in the declarations for a class to be unassignable. ++#if !defined(DISALLOW_ASSIGN) + #define DISALLOW_ASSIGN(TypeName) \ + void operator=(const TypeName&) = delete ++#endif + + // A macro to disallow the copy constructor and operator= functions. + // This should be used in the private: declarations for a class. ++#if !defined(DISALLOW_COPY_AND_ASSIGN) + #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete ++#endif + + // A macro to disallow all the implicit constructors, namely the + // default constructor, copy constructor and operator= functions. +@@ -32,9 +45,11 @@ + // This should be used in the private: declarations for a class + // that wants to prevent anyone from instantiating it. This is + // especially useful for classes containing only static methods. ++#if !defined(DISALLOW_IMPLICIT_CONSTRUCTORS) + #define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + DISALLOW_COPY_AND_ASSIGN(TypeName) ++#endif + + // The arraysize(arr) macro returns the # of elements in an array arr. The + // expression is a compile-time constant, and therefore can be used in defining +@@ -45,8 +60,10 @@ + // This template function declaration is used in defining arraysize. + // Note that the function doesn't need an implementation, as we only + // use its type. ++#if !defined(arraysize) + template char (&ArraySizeHelper(T (&array)[N]))[N]; + #define arraysize(array) (sizeof(ArraySizeHelper(array))) ++#endif + + // Used to explicitly mark the return value of a function as unused. If you are + // really sure you don't want to do anything with the return value of a function +@@ -79,8 +96,10 @@ enum LinkerInitialized { LINKER_INITIALI + // Use these to declare and define a static local variable (static T;) so that + // it is leaked so that its destructors are not called at exit. If you need + // thread-safe initialization, use base/lazy_instance.h instead. ++#if !defined(CR_DEFINE_STATIC_LOCAL) + #define CR_DEFINE_STATIC_LOCAL(type, name, arguments) \ + static type& name = *new type arguments ++#endif + + } // base + diff --git a/libchrome_tools/patch/message_loop.patch b/libchrome_tools/patch/message_loop.patch new file mode 100644 index 0000000000000000000000000000000000000000..0ab36300ebd1fe2c9353c32a0496c67cbbbcc972 --- /dev/null +++ b/libchrome_tools/patch/message_loop.patch @@ -0,0 +1,13 @@ +--- a/base/message_loop/message_loop.h ++++ b/base/message_loop/message_loop.h +@@ -565,7 +565,9 @@ class BASE_EXPORT MessageLoopForIO : pub + // Returns the MessageLoopForIO of the current thread. + static MessageLoopForIO* current() { + MessageLoop* loop = MessageLoop::current(); +- DCHECK(loop); ++ DCHECK(loop) << "Can't call MessageLoopForIO::current() when no message " ++ "loop was created for this thread. Use " ++ " MessageLoop::current() or MessageLoopForIO::IsCurrent()."; + DCHECK_EQ(MessageLoop::TYPE_IO, loop->type()); + return static_cast(loop); + } diff --git a/libchrome_tools/patch/modp_b64.patch b/libchrome_tools/patch/modp_b64.patch new file mode 100644 index 0000000000000000000000000000000000000000..89d838c8550467fc7d6e0c0bf21653d0ffd8cf3a --- /dev/null +++ b/libchrome_tools/patch/modp_b64.patch @@ -0,0 +1,19 @@ +--- /dev/null ++++ b/third_party/modp_b64/modp_b64.h +@@ -0,0 +1,16 @@ ++// Copyright (C) 2018 The Android Open Source Project ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++ ++// Redirect to system header. ++#include diff --git a/libchrome_tools/patch/mojo-Add-a-way-to-handle-unhandled-RuntimeExceptions.patch b/libchrome_tools/patch/mojo-Add-a-way-to-handle-unhandled-RuntimeExceptions.patch new file mode 100644 index 0000000000000000000000000000000000000000..f5ac205ce39b0aaa80125d1d48af928e307eb8bb --- /dev/null +++ b/libchrome_tools/patch/mojo-Add-a-way-to-handle-unhandled-RuntimeExceptions.patch @@ -0,0 +1,129 @@ +From 4256ecec730fdf5a41f34e11c0641e072971cb8c Mon Sep 17 00:00:00 2001 +From: Luis Hector Chavez +Date: Mon, 18 Jun 2018 20:14:56 +0000 +Subject: [PATCH] [mojo] Add a way to handle unhandled RuntimeExceptions + +This change makes it possible to allow interfaces to globally handle +unhandled RuntimeExceptions, in their bindings or in the callbacks. + + delegate can now forward the unhandled exceptions to the crash + server. + +Bug: 810087 +Test: Android-on-Chrome OS has the same behavior as before +Test: Android-on-Chrome OS, when setting the DefaultExceptionHandler's +Change-Id: I2b7455a0344a109e1d2416a74ad4a0b98cd007f0 +Reviewed-on: https://chromium-review.googlesource.com/1101898 +Reviewed-by: Ken Rockot +Commit-Queue: Luis Hector Chavez +Cr-Commit-Position: refs/heads/master@{#568128} +--- + mojo/public/java/BUILD.gn | 1 + + .../org/chromium/mojo/bindings/Connector.java | 12 +++- + .../mojo/bindings/ExceptionHandler.java | 59 +++++++++++++++++++ + 3 files changed, 70 insertions(+), 2 deletions(-) + create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExceptionHandler.java + +diff --git a/mojo/public/java/BUILD.gn b/mojo/public/java/BUILD.gn +index 14951f4f0959..259e09cf7c07 100644 +--- a/mojo/public/java/BUILD.gn ++++ b/mojo/public/java/BUILD.gn +@@ -41,6 +41,7 @@ android_library("bindings_java") { + "bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java", + "bindings/src/org/chromium/mojo/bindings/DeserializationException.java", + "bindings/src/org/chromium/mojo/bindings/Encoder.java", ++ "bindings/src/org/chromium/mojo/bindings/ExceptionHandler.java", + "bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java", + "bindings/src/org/chromium/mojo/bindings/HandleOwner.java", + "bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java", +diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java +index 3a6d67112ce0..45f1fc7462e8 100644 +--- a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java ++++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java +@@ -201,8 +201,16 @@ public class Connector implements MessageReceiver, HandleOwner(result.getMojoResult(), accepted); + } + return new ResultAnd(result.getMojoResult(), false); +diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExceptionHandler.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExceptionHandler.java +new file mode 100644 +index 000000000000..8961d22d3ee4 +--- /dev/null ++++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExceptionHandler.java +@@ -0,0 +1,59 @@ ++// 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. ++ ++package org.chromium.mojo.bindings; ++ ++/** ++ * An {@link ExceptionHandler} is notified of any {@link RuntimeException} happening in the ++ * bindings or any of the callbacks. ++ */ ++public interface ExceptionHandler { ++ /** ++ * Receives a notification that an unhandled {@link RuntimeException} has been thrown in an ++ * {@link Interface} implementation or one of the {@link Callbacks} internal classes. ++ * ++ * Normal implementations should either throw the exception or return whether the connection ++ * should be kept alive or terminated. ++ */ ++ public boolean handleException(RuntimeException e); ++ ++ /** ++ * The default ExceptionHandler, which simply throws the exception upon receiving it. It can ++ * also delegate the handling of the exceptions to another instance of ExceptionHandler. ++ */ ++ public static class DefaultExceptionHandler implements ExceptionHandler { ++ private ExceptionHandler mDelegate; ++ ++ @Override ++ public boolean handleException(RuntimeException e) { ++ if (mDelegate != null) { ++ return mDelegate.handleException(e); ++ } ++ throw e; ++ } ++ ++ private DefaultExceptionHandler() {} ++ ++ /** ++ * Static class that implements the initialization-on-demand holder idiom. ++ */ ++ private static class LazyHolder { ++ static final DefaultExceptionHandler INSTANCE = new DefaultExceptionHandler(); ++ } ++ ++ /** ++ * Gets the singleton instance for the DefaultExceptionHandler. ++ */ ++ public static DefaultExceptionHandler getInstance() { ++ return LazyHolder.INSTANCE; ++ } ++ ++ /** ++ * Sets a delegate ExceptionHandler, in case throwing an exception is not desirable. ++ */ ++ public void setDelegate(ExceptionHandler exceptionHandler) { ++ mDelegate = exceptionHandler; ++ } ++ } ++} +-- +2.18.0.203.gfac676dfb9-goog + diff --git a/libchrome_tools/patch/mojo-Avoid-a-crash-when-NodeController-pending-invit.patch b/libchrome_tools/patch/mojo-Avoid-a-crash-when-NodeController-pending-invit.patch new file mode 100644 index 0000000000000000000000000000000000000000..082d17eba6932ebe4c4b42d0c37cf4ced29432d4 --- /dev/null +++ b/libchrome_tools/patch/mojo-Avoid-a-crash-when-NodeController-pending-invit.patch @@ -0,0 +1,44 @@ +From 30a0b449c8f7036c300d808db96b391220b7698f Mon Sep 17 00:00:00 2001 +From: Luis Hector Chavez +Date: Wed, 23 May 2018 00:39:19 +0000 +Subject: [PATCH] [mojo]: Avoid a crash when NodeController pending invitations + diverge + +This change avoids a crash when NodeController::pending_broker_clients_ +and NodeController::pending_invitations_ diverge. This might happen with +complex enough node topologies, where there is a node that proxies +invitations to a set of other nodes. + +BUG=845709 + +Change-Id: Ia678f464fafb69628600ec00dac19da3b6868fe0 +Reviewed-on: https://chromium-review.googlesource.com/1069728 +Reviewed-by: Ken Rockot +Commit-Queue: Luis Hector Chavez +Cr-Commit-Position: refs/heads/master@{#560857} +--- + mojo/edk/system/node_controller.cc | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/mojo/edk/system/node_controller.cc b/mojo/edk/system/node_controller.cc +index 73b16b1..e608f0c 100644 +--- a/mojo/edk/system/node_controller.cc ++++ b/mojo/edk/system/node_controller.cc +@@ -1102,8 +1102,13 @@ void NodeController::OnAcceptBrokerClient(const ports::NodeName& from_node, + while (!pending_broker_clients.empty()) { + const ports::NodeName& child_name = pending_broker_clients.front(); + auto it = pending_children_.find(child_name); +- DCHECK(it != pending_children_.end()); +- broker->AddBrokerClient(child_name, it->second->CopyRemoteProcessHandle()); ++ // If for any reason we don't have a pending invitation for the invitee, ++ // there's nothing left to do: we've already swapped the relevant state into ++ // the stack. ++ if (it != pending_children_.end()) { ++ broker->AddBrokerClient(child_name, ++ it->second->CopyRemoteProcessHandle()); ++ } + pending_broker_clients.pop(); + } + +-- +2.17.0.921.gf22659ad46-goog diff --git a/libchrome_tools/patch/mojo.patch b/libchrome_tools/patch/mojo.patch new file mode 100644 index 0000000000000000000000000000000000000000..d86b735380b5f7dc9a81b473b049a8a211b3761e --- /dev/null +++ b/libchrome_tools/patch/mojo.patch @@ -0,0 +1,519 @@ +# Local patches for libmojo. + +--- a/mojo/android/system/base_run_loop.cc ++++ b/mojo/android/system/base_run_loop.cc +@@ -6,9 +6,10 @@ + + #include + +-#include "base/android/base_jni_registrar.h" ++// Removed unused headers. TODO(hidehiko): Upstream. ++// #include "base/android/base_jni_registrar.h" + #include "base/android/jni_android.h" +-#include "base/android/jni_registrar.h" ++// #include "base/android/jni_registrar.h" + #include "base/bind.h" + #include "base/logging.h" + #include "base/message_loop/message_loop.h" +@@ -79,4 +80,3 @@ bool RegisterBaseRunLoop(JNIEnv* env) { + + } // namespace android + } // namespace mojo +- +--- a/mojo/android/system/core_impl.cc ++++ b/mojo/android/system/core_impl.cc +@@ -7,10 +7,11 @@ + #include + #include + +-#include "base/android/base_jni_registrar.h" ++// Removed unused headers. TODO(hidehiko): Upstream. ++// #include "base/android/base_jni_registrar.h" + #include "base/android/jni_android.h" +-#include "base/android/jni_registrar.h" +-#include "base/android/library_loader/library_loader_hooks.h" ++// #include "base/android/jni_registrar.h" ++// #include "base/android/library_loader/library_loader_hooks.h" + #include "base/android/scoped_java_ref.h" + #include "jni/CoreImpl_jni.h" + #include "mojo/public/c/system/core.h" +--- a/mojo/android/system/watcher_impl.cc ++++ b/mojo/android/system/watcher_impl.cc +@@ -7,10 +7,11 @@ + #include + #include + +-#include "base/android/base_jni_registrar.h" ++// Removed unused headers. TODO(hidehiko): Upstream. ++// #include "base/android/base_jni_registrar.h" + #include "base/android/jni_android.h" +-#include "base/android/jni_registrar.h" +-#include "base/android/library_loader/library_loader_hooks.h" ++// #include "base/android/jni_registrar.h" ++// #include "base/android/library_loader/library_loader_hooks.h" + #include "base/android/scoped_java_ref.h" + #include "base/bind.h" + #include "jni/WatcherImpl_jni.h" +--- a/mojo/common/common_custom_types_struct_traits.h ++++ b/mojo/common/common_custom_types_struct_traits.h +@@ -63,19 +63,6 @@ struct StructTraits +-struct StructTraits { +- static int64_t microseconds(const base::TimeDelta& delta) { +- return delta.InMicroseconds(); +- } +- +- static bool Read(common::mojom::TimeDeltaDataView data, +- base::TimeDelta* delta) { +- *delta = base::TimeDelta::FromMicroseconds(data.microseconds()); +- return true; +- } +-}; +- +-template <> + struct StructTraits { + static bool IsNull(const base::File& file) { return !file.IsValid(); } + +--- a/mojo/common/time.mojom ++++ b/mojo/common/time.mojom +@@ -4,12 +4,18 @@ + + module mojo.common.mojom; + +-[Native] +-struct Time; ++struct Time { ++ // The internal value is expressed in terms of microseconds since a fixed but ++ // intentionally unspecified epoch. ++ int64 internal_value; ++}; + + struct TimeDelta { + int64 microseconds; + }; + +-[Native] +-struct TimeTicks; ++struct TimeTicks { ++ // The internal value is expressed in terms of microseconds since a fixed but ++ // intentionally unspecified epoch. ++ int64 internal_value; ++}; +--- a/mojo/edk/embedder/platform_channel_pair_posix.cc ++++ b/mojo/edk/embedder/platform_channel_pair_posix.cc +@@ -35,7 +35,7 @@ namespace edk { + + namespace { + +-#if defined(OS_ANDROID) ++#if defined(OS_ANDROID) || defined(__ANDROID__) + enum { + // Leave room for any other descriptors defined in content for example. + // TODO(jcivelli): consider changing base::GlobalDescriptors to generate a +@@ -102,7 +102,7 @@ ScopedPlatformHandle + PlatformChannelPair::PassClientHandleFromParentProcessFromString( + const std::string& value) { + int client_fd = -1; +-#if defined(OS_ANDROID) ++#if defined(OS_ANDROID) || defined(__ANDROID__) + base::GlobalDescriptors::Key key = -1; + if (value.empty() || !base::StringToUint(value, &key)) { + LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch; +@@ -142,7 +142,7 @@ void PlatformChannelPair::PrepareToPassC + std::string + PlatformChannelPair::PrepareToPassClientHandleToChildProcessAsString( + HandlePassingInformation* handle_passing_info) const { +-#if defined(OS_ANDROID) ++#if defined(OS_ANDROID) || defined(__ANDROID__) + int fd = client_handle_.get().handle; + handle_passing_info->push_back( + std::pair(fd, kAndroidClientHandleDescriptor)); +--- a/mojo/edk/system/ports/node.cc ++++ b/mojo/edk/system/ports/node.cc +@@ -803,7 +803,7 @@ scoped_refptr Node::GetPort_Locked + if (iter == ports_.end()) + return nullptr; + +-#if defined(OS_ANDROID) && defined(ARCH_CPU_ARM64) ++#if (defined(OS_ANDROID) || defined(__ANDROID__)) && defined(ARCH_CPU_ARM64) + // Workaround for https://crbug.com/665869. + base::subtle::MemoryBarrier(); + #endif +--- a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java ++++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java +@@ -171,20 +171,23 @@ public class RouterImpl implements Route + assert messageWithHeader.getHeader().hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG); + + // Compute a request id for being able to route the response. +- long requestId = mNextRequestId++; +- // Reserve 0 in case we want it to convey special meaning in the future. +- if (requestId == 0) { +- requestId = mNextRequestId++; +- } +- if (mResponders.containsKey(requestId)) { +- throw new IllegalStateException("Unable to find a new request identifier."); +- } +- messageWithHeader.setRequestId(requestId); +- if (!mConnector.accept(messageWithHeader)) { +- return false; ++ // TODO(lhchavez): Remove this hack. See b/28986534 for details. ++ synchronized (mResponders) { ++ long requestId = mNextRequestId++; ++ // Reserve 0 in case we want it to convey special meaning in the future. ++ if (requestId == 0) { ++ requestId = mNextRequestId++; ++ } ++ if (mResponders.containsKey(requestId)) { ++ throw new IllegalStateException("Unable to find a new request identifier."); ++ } ++ messageWithHeader.setRequestId(requestId); ++ if (!mConnector.accept(messageWithHeader)) { ++ return false; ++ } ++ // Only keep the responder is the message has been accepted. ++ mResponders.put(requestId, responder); + } +- // Only keep the responder is the message has been accepted. +- mResponders.put(requestId, responder); + return true; + } + +@@ -227,11 +230,15 @@ public class RouterImpl implements Route + return false; + } else if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) { + long requestId = header.getRequestId(); +- MessageReceiver responder = mResponders.get(requestId); +- if (responder == null) { +- return false; ++ MessageReceiver responder; ++ // TODO(lhchavez): Remove this hack. See b/28986534 for details. ++ synchronized (mResponders) { ++ responder = mResponders.get(requestId); ++ if (responder == null) { ++ return false; ++ } ++ mResponders.remove(requestId); + } +- mResponders.remove(requestId); + return responder.accept(message); + } else { + if (mIncomingMessageReceiver != null) { +--- a/base/android/jni_android.cc ++++ b/base/android/jni_android.cc +@@ -10,7 +10,8 @@ + + #include "base/android/build_info.h" + #include "base/android/jni_string.h" +-#include "base/android/jni_utils.h" ++// Removed unused headers. TODO(hidehiko): Upstream. ++// #include "base/android/jni_utils.h" + #include "base/debug/debugging_flags.h" + #include "base/lazy_instance.h" + #include "base/logging.h" +@@ -240,7 +241,16 @@ void CheckException(JNIEnv* env) { + } + + // Now, feel good about it and die. +- LOG(FATAL) << "Please include Java exception stack in crash report"; ++ // TODO(lhchavez): Remove this hack. See b/28814913 for details. ++ // We're using BuildInfo's java_exception_info() instead of storing the ++ // exception info a few lines above to avoid extra copies. It will be ++ // truncated to 1024 bytes anyways. ++ const char* exception_string = ++ base::android::BuildInfo::GetInstance()->java_exception_info(); ++ if (exception_string) ++ LOG(FATAL) << exception_string; ++ else ++ LOG(FATAL) << "Unhandled exception"; + } + + std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { +--- a/build/android/gyp/util/build_utils.py ++++ b/build/android/gyp/util/build_utils.py +@@ -20,7 +20,13 @@ import zipfile + # Some clients do not add //build/android/gyp to PYTHONPATH. + import md5_check # pylint: disable=relative-import + +-sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) ++# pylib conflicts with mojo/public/tools/bindings/pylib. Prioritize ++# build/android/pylib. ++# PYTHONPATH wouldn't help in this case, because soong put source files under ++# temp directory for each build, so the abspath is unknown until the ++# execution. ++# sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) ++sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) + from pylib.constants import host_paths + + sys.path.append(os.path.join(os.path.dirname(__file__), +@@ -581,4 +587,3 @@ def CallAndWriteDepfileIfStale(function, + output_paths=output_paths, + force=force, + pass_changes=True) +- +--- a/build/android/pylib/constants/__init__.py ++++ b/build/android/pylib/constants/__init__.py +@@ -96,7 +96,7 @@ DEVICE_PERF_OUTPUT_DIR = ( + SCREENSHOTS_DIR = os.path.join(DIR_SOURCE_ROOT, 'out_screenshots') + + ANDROID_SDK_VERSION = version_codes.MARSHMALLOW +-ANDROID_SDK_BUILD_TOOLS_VERSION = '25.0.2' ++ANDROID_SDK_BUILD_TOOLS_VERSION = '24.0.2' + ANDROID_SDK_ROOT = os.path.join(DIR_SOURCE_ROOT, + 'third_party', 'android_tools', 'sdk') + ANDROID_SDK_TOOLS = os.path.join(ANDROID_SDK_ROOT, +--- /dev/null ++++ b/gen/mojo/common/common_custom_types__type_mappings +@@ -0,0 +1,193 @@ ++{ ++ "c++": { ++ "mojo.common.mojom.Value": { ++ "hashable": false, ++ "typename": "std::unique_ptr", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/values_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": true, ++ "nullable_is_same_type": true, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/values.h" ++ ] ++ }, ++ "mojo.common.mojom.UnguessableToken": { ++ "hashable": false, ++ "typename": "base::UnguessableToken", ++ "traits_headers": [ ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/unguessable_token.h" ++ ] ++ }, ++ "mojo.common.mojom.TextDirection": { ++ "hashable": false, ++ "typename": "base::i18n::TextDirection", ++ "traits_headers": [ ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/i18n/rtl.h" ++ ] ++ }, ++ "mojo.common.mojom.ListValue": { ++ "hashable": false, ++ "typename": "std::unique_ptr", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/values_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": true, ++ "nullable_is_same_type": true, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/values.h" ++ ] ++ }, ++ "mojo.common.mojom.String16": { ++ "hashable": false, ++ "typename": "base::string16", ++ "traits_headers": [ ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/strings/string16.h" ++ ] ++ }, ++ "mojo.common.mojom.Time": { ++ "hashable": false, ++ "typename": "base::Time", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": true, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/time/time.h" ++ ] ++ }, ++ "mojo.common.mojom.TimeDelta": { ++ "hashable": false, ++ "typename": "base::TimeDelta", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": true, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/time/time.h" ++ ] ++ }, ++ "mojo.common.mojom.TimeTicks": { ++ "hashable": false, ++ "typename": "base::TimeTicks", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": true, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/time/time.h" ++ ] ++ }, ++ "mojo.common.mojom.LegacyListValue": { ++ "hashable": false, ++ "typename": "base::ListValue", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/values_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": true, ++ "public_headers": [ ++ "base/values.h" ++ ] ++ }, ++ "mojo.common.mojom.File": { ++ "hashable": false, ++ "typename": "base::File", ++ "traits_headers": [ ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": true, ++ "nullable_is_same_type": true, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/files/file.h" ++ ] ++ }, ++ "mojo.common.mojom.FilePath": { ++ "hashable": false, ++ "typename": "base::FilePath", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/files/file_path.h" ++ ] ++ }, ++ "mojo.common.mojom.DictionaryValue": { ++ "hashable": false, ++ "typename": "std::unique_ptr", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/values_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": true, ++ "nullable_is_same_type": true, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/values.h" ++ ] ++ }, ++ "mojo.common.mojom.Version": { ++ "hashable": false, ++ "typename": "base::Version", ++ "traits_headers": [ ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/version.h" ++ ] ++ } ++ } ++} +--- /dev/null ++++ b/mojo/common/time_struct_traits.h +@@ -0,0 +1,55 @@ ++// 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. ++ ++#ifndef MOJO_COMMON_TIME_STRUCT_TRAITS_H_ ++#define MOJO_COMMON_TIME_STRUCT_TRAITS_H_ ++ ++#include "base/time/time.h" ++#include "mojo/common/time.mojom-shared.h" ++ ++namespace mojo { ++ ++template <> ++struct StructTraits { ++ static int64_t internal_value(const base::Time& time) { ++ return time.since_origin().InMicroseconds(); ++ } ++ ++ static bool Read(common::mojom::TimeDataView data, base::Time* time) { ++ *time = ++ base::Time() + base::TimeDelta::FromMicroseconds(data.internal_value()); ++ return true; ++ } ++}; ++ ++template <> ++struct StructTraits { ++ static int64_t microseconds(const base::TimeDelta& delta) { ++ return delta.InMicroseconds(); ++ } ++ ++ static bool Read(common::mojom::TimeDeltaDataView data, ++ base::TimeDelta* delta) { ++ *delta = base::TimeDelta::FromMicroseconds(data.microseconds()); ++ return true; ++ } ++}; ++ ++template <> ++struct StructTraits { ++ static int64_t internal_value(const base::TimeTicks& time) { ++ return time.since_origin().InMicroseconds(); ++ } ++ ++ static bool Read(common::mojom::TimeTicksDataView data, ++ base::TimeTicks* time) { ++ *time = base::TimeTicks() + ++ base::TimeDelta::FromMicroseconds(data.internal_value()); ++ return true; ++ } ++}; ++ ++} // namespace mojo ++ ++#endif // MOJO_COMMON_TIME_STRUCT_TRAITS_H_ diff --git a/libchrome_tools/patch/path_service.patch b/libchrome_tools/patch/path_service.patch new file mode 100644 index 0000000000000000000000000000000000000000..75b30dc044900280c48c96e6e52e258fb238ef3d --- /dev/null +++ b/libchrome_tools/patch/path_service.patch @@ -0,0 +1,82 @@ +# Several paths are not supported in PathService by libchrome. + +--- a/base/base_paths_posix.cc ++++ b/base/base_paths_posix.cc +@@ -19,7 +19,8 @@ + #include "base/files/file_path.h" + #include "base/files/file_util.h" + #include "base/logging.h" +-#include "base/nix/xdg_util.h" ++// Unused, and this file is not ported to libchrome. ++// #include "base/nix/xdg_util.h" + #include "base/path_service.h" + #include "base/process/process_metrics.h" + #include "build/build_config.h" +@@ -77,6 +78,8 @@ bool PathProviderPosix(int key, FilePath + return true; + #endif + } ++// Following paths are not supported in libchrome/libmojo. ++#if 0 + case DIR_SOURCE_ROOT: { + // Allow passing this in the environment, for more flexibility in build + // tree configurations (sub-project builds, gyp --output_dir, etc.) +@@ -112,6 +115,7 @@ bool PathProviderPosix(int key, FilePath + *result = cache_dir; + return true; + } ++#endif + } + return false; + } +--- a/base/files/file_util_posix.cc ++++ b/base/files/file_util_posix.cc +@@ -533,6 +533,8 @@ bool GetTempDir(FilePath* path) { + } else { + #if defined(OS_ANDROID) + return PathService::Get(base::DIR_CACHE, path); ++#elif defined(__ANDROID__) ++ *path = FilePath("/data/local/tmp"); + #else + *path = FilePath("/tmp"); + #endif +--- a/base/json/json_reader_unittest.cc ++++ b/base/json/json_reader_unittest.cc +@@ -567,7 +567,7 @@ TEST(JSONReaderTest, Reading) { + } + } + +-TEST(JSONReaderTest, ReadFromFile) { ++TEST(JSONReaderTest, DISABLED_ReadFromFile) { + FilePath path; + ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &path)); + path = path.AppendASCII("json"); +--- a/base/json/json_value_serializer_unittest.cc ++++ b/base/json/json_value_serializer_unittest.cc +@@ -402,7 +402,7 @@ class JSONFileValueSerializerTest : publ + ScopedTempDir temp_dir_; + }; + +-TEST_F(JSONFileValueSerializerTest, Roundtrip) { ++TEST_F(JSONFileValueSerializerTest, DISABLED_Roundtrip) { + FilePath original_file_path; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &original_file_path)); + original_file_path = original_file_path.AppendASCII("serializer_test.json"); +@@ -445,7 +445,7 @@ TEST_F(JSONFileValueSerializerTest, Roun + EXPECT_TRUE(DeleteFile(written_file_path, false)); + } + +-TEST_F(JSONFileValueSerializerTest, RoundtripNested) { ++TEST_F(JSONFileValueSerializerTest, DISABLED_RoundtripNested) { + FilePath original_file_path; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &original_file_path)); + original_file_path = +@@ -471,7 +471,7 @@ TEST_F(JSONFileValueSerializerTest, Roun + EXPECT_TRUE(DeleteFile(written_file_path, false)); + } + +-TEST_F(JSONFileValueSerializerTest, NoWhitespace) { ++TEST_F(JSONFileValueSerializerTest, DISABLED_NoWhitespace) { + FilePath source_file_path; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &source_file_path)); + source_file_path = diff --git a/libchrome_tools/patch/protobuf.patch b/libchrome_tools/patch/protobuf.patch new file mode 100644 index 0000000000000000000000000000000000000000..343da9c4566799771244c282802e96142a5569ca --- /dev/null +++ b/libchrome_tools/patch/protobuf.patch @@ -0,0 +1,4 @@ +--- /dev/null ++++ b/third_party/protobuf/src/google/protobuf/message_lite.h +@@ -0,0 +1 @@ ++#include diff --git a/libchrome_tools/patch/shared_memory_posix.patch b/libchrome_tools/patch/shared_memory_posix.patch new file mode 100644 index 0000000000000000000000000000000000000000..abae9f455d7b69a67fcad9d7ee40601ba4f112fb --- /dev/null +++ b/libchrome_tools/patch/shared_memory_posix.patch @@ -0,0 +1,56 @@ +--- a/base/memory/shared_memory_posix.cc ++++ b/base/memory/shared_memory_posix.cc +@@ -27,6 +27,8 @@ + + #if defined(OS_ANDROID) + #include "base/os_compat_android.h" ++#endif ++#if defined(OS_ANDROID) || defined(__ANDROID__) + #include "third_party/ashmem/ashmem.h" + #endif + +@@ -96,7 +98,7 @@ bool SharedMemory::CreateAndMapAnonymous + return CreateAnonymous(size) && Map(size); + } + +-#if !defined(OS_ANDROID) ++#if !defined(OS_ANDROID) && !defined(__ANDROID__) + // static + bool SharedMemory::GetSizeFromSharedMemoryHandle( + const SharedMemoryHandle& handle, +@@ -255,7 +257,7 @@ bool SharedMemory::Open(const std::strin + return PrepareMapFile(std::move(fp), std::move(readonly_fd), &mapped_file_, + &readonly_mapped_file_); + } +-#endif // !defined(OS_ANDROID) ++#endif // !defined(OS_ANDROID) && !defined(__ANDROID__) + + bool SharedMemory::MapAt(off_t offset, size_t bytes) { + if (mapped_file_ == -1) +@@ -267,7 +269,7 @@ bool SharedMemory::MapAt(off_t offset, s + if (memory_) + return false; + +-#if defined(OS_ANDROID) ++#if defined(OS_ANDROID) || defined(__ANDROID__) + // On Android, Map can be called with a size and offset of zero to use the + // ashmem-determined size. + if (bytes == 0) { +@@ -332,7 +334,7 @@ void SharedMemory::Close() { + } + } + +-#if !defined(OS_ANDROID) ++#if !defined(OS_ANDROID) && !defined(__ANDROID__) + // For the given shmem named |mem_name|, return a filename to mmap() + // (and possibly create). Modifies |filename|. Return false on + // error, or true of we are happy. +@@ -355,7 +357,7 @@ bool SharedMemory::FilePathForMemoryName + *path = temp_dir.AppendASCII(name_base + ".shmem." + mem_name); + return true; + } +-#endif // !defined(OS_ANDROID) ++#endif // !defined(OS_ANDROID) && !defined(__ANDROID__) + + bool SharedMemory::ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle* new_handle, diff --git a/libchrome_tools/patch/ssl.patch b/libchrome_tools/patch/ssl.patch new file mode 100644 index 0000000000000000000000000000000000000000..f4a2f8f83ec82c8089cfc0258543aea8414142fe --- /dev/null +++ b/libchrome_tools/patch/ssl.patch @@ -0,0 +1,126 @@ +# Chrome asumes boringssl, while system installed ssl library may not. + +--- a/crypto/openssl_util.cc ++++ b/crypto/openssl_util.cc +@@ -4,6 +4,13 @@ + + #include "crypto/openssl_util.h" + ++#if defined(OPENSSL_IS_BORINGSSL) ++#include ++#else ++#include ++#endif ++#include ++#include + #include + #include + +@@ -11,8 +18,6 @@ + + #include "base/logging.h" + #include "base/strings/string_piece.h" +-#include "third_party/boringssl/src/include/openssl/crypto.h" +-#include "third_party/boringssl/src/include/openssl/err.h" + + namespace crypto { + +@@ -35,8 +40,12 @@ int OpenSSLErrorCallback(const char* str + } // namespace + + void EnsureOpenSSLInit() { ++#if defined(OPENSSL_IS_BORINGSSL) + // CRYPTO_library_init may be safely called concurrently. + CRYPTO_library_init(); ++#else ++ SSL_library_init(); ++#endif + } + + void ClearOpenSSLERRStack(const tracked_objects::Location& location) { +--- a/crypto/rsa_private_key.h ++++ b/crypto/rsa_private_key.h +@@ -7,6 +7,7 @@ + + #include + #include ++#include + + #include + #include +@@ -14,7 +15,6 @@ + #include "base/macros.h" + #include "build/build_config.h" + #include "crypto/crypto_export.h" +-#include "third_party/boringssl/src/include/openssl/base.h" + + namespace crypto { + +--- a/crypto/secure_hash.cc ++++ b/crypto/secure_hash.cc +@@ -4,14 +4,18 @@ + + #include "crypto/secure_hash.h" + ++#if defined(OPENSSL_IS_BORINGSSL) ++#include ++#else ++#include ++#endif ++#include + #include + + #include "base/logging.h" + #include "base/memory/ptr_util.h" + #include "base/pickle.h" + #include "crypto/openssl_util.h" +-#include "third_party/boringssl/src/include/openssl/mem.h" +-#include "third_party/boringssl/src/include/openssl/sha.h" + + namespace crypto { + +--- a/crypto/signature_verifier.h ++++ b/crypto/signature_verifier.h +@@ -54,9 +54,9 @@ class CRYPTO_EXPORT SignatureVerifier { + // subjectPublicKey BIT STRING } + bool VerifyInit(SignatureAlgorithm signature_algorithm, + const uint8_t* signature, +- size_t signature_len, ++ int signature_len, + const uint8_t* public_key_info, +- size_t public_key_info_len); ++ int public_key_info_len); + + // Initiates a RSA-PSS signature verification operation. This should be + // followed by one or more VerifyUpdate calls and a VerifyFinal call. +@@ -76,14 +76,14 @@ class CRYPTO_EXPORT SignatureVerifier { + // subjectPublicKey BIT STRING } + bool VerifyInitRSAPSS(HashAlgorithm hash_alg, + HashAlgorithm mask_hash_alg, +- size_t salt_len, ++ int salt_len, + const uint8_t* signature, +- size_t signature_len, ++ int signature_len, + const uint8_t* public_key_info, +- size_t public_key_info_len); ++ int public_key_info_len); + + // Feeds a piece of the data to the signature verifier. +- void VerifyUpdate(const uint8_t* data_part, size_t data_part_len); ++ void VerifyUpdate(const uint8_t* data_part, int data_part_len); + + // Concludes a signature verification operation. Returns true if the + // signature is valid. Returns false if the signature is invalid or an +@@ -94,9 +94,9 @@ class CRYPTO_EXPORT SignatureVerifier { + bool CommonInit(int pkey_type, + const EVP_MD* digest, + const uint8_t* signature, +- size_t signature_len, ++ int signature_len, + const uint8_t* public_key_info, +- size_t public_key_info_len, ++ int public_key_info_len, + EVP_PKEY_CTX** pkey_ctx); + + void Reset(); diff --git a/libchrome_tools/patch/statfs_f_type.patch b/libchrome_tools/patch/statfs_f_type.patch new file mode 100644 index 0000000000000000000000000000000000000000..989380b5da38d104cbd5731383f79676169c9641 --- /dev/null +++ b/libchrome_tools/patch/statfs_f_type.patch @@ -0,0 +1,40 @@ +# In some platforms, |statfs_buf.f_type| is declared as signed, but some of the +# values will overflow it, causing narrowing warnings. Cast to the largest +# possible unsigned integer type to avoid it. + +--- a/base/files/file_util_linux.cc ++++ b/base/files/file_util_linux.cc +@@ -6,6 +6,7 @@ + + #include + #include ++#include + #include + + #include "base/files/file_path.h" +@@ -23,7 +24,10 @@ bool GetFileSystemType(const FilePath& p + + // Not all possible |statfs_buf.f_type| values are in linux/magic.h. + // Missing values are copied from the statfs man page. +- switch (statfs_buf.f_type) { ++ // In some platforms, |statfs_buf.f_type| is declared as signed, but some of ++ // the values will overflow it, causing narrowing warnings. Cast to the ++ // largest possible unsigned integer type to avoid it. ++ switch (static_cast(statfs_buf.f_type)) { + case 0: + *type = FILE_SYSTEM_0; + break; +--- a/base/sys_info_posix.cc ++++ b/base/sys_info_posix.cc +@@ -85,7 +85,10 @@ bool IsStatsZeroIfUnlimited(const base:: + if (HANDLE_EINTR(statfs(path.value().c_str(), &stats)) != 0) + return false; + +- switch (stats.f_type) { ++ // In some platforms, |statfs_buf.f_type| is declared as signed, but some of ++ // the values will overflow it, causing narrowing warnings. Cast to the ++ // largest possible unsigned integer type to avoid it. ++ switch (static_cast(stats.f_type)) { + case TMPFS_MAGIC: + case HUGETLBFS_MAGIC: + case RAMFS_MAGIC: diff --git a/libchrome_tools/patch/subprocess.patch b/libchrome_tools/patch/subprocess.patch new file mode 100644 index 0000000000000000000000000000000000000000..ff1d02d3dd9b338e59b9b0d3038c1be5f18ddf96 --- /dev/null +++ b/libchrome_tools/patch/subprocess.patch @@ -0,0 +1,74 @@ +--- a/base/process/process_metrics_unittest.cc ++++ b/base/process/process_metrics_unittest.cc +@@ -549,6 +549,9 @@ MULTIPROCESS_TEST_MAIN(ChildMain) { + + } // namespace + ++// ARC note: don't compile as SpawnMultiProcessTestChild brings in a lot of ++// extra dependency. ++#if !defined(OS_ANDROID) && !defined(__ANDROID__) && !defined(__ANDROID_HOST__) + TEST(ProcessMetricsTest, GetOpenFdCount) { + ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); +@@ -562,9 +565,23 @@ TEST(ProcessMetricsTest, GetOpenFdCount) + + std::unique_ptr metrics( + ProcessMetrics::CreateProcessMetrics(spawn_child.process.Handle())); +- EXPECT_EQ(0, metrics->GetOpenFdCount()); ++ // Try a couple times to observe the child with 0 fds open. ++ // Sometimes we've seen that the child can have 1 remaining ++ // fd shortly after receiving the signal. Potentially this ++ // is actually the signal file still open in the child. ++ int open_fds = -1; ++ for (int tries = 0; tries < 5; ++tries) { ++ open_fds = metrics->GetOpenFdCount(); ++ if (!open_fds) { ++ break; ++ } ++ PlatformThread::Sleep(TimeDelta::FromMilliseconds(1)); ++ } ++ EXPECT_EQ(0, open_fds); + ASSERT_TRUE(spawn_child.process.Terminate(0, true)); + } ++#endif // !defined(__ANDROID__) ++ + #endif // defined(OS_LINUX) + + } // namespace debug +--- a/base/test/multiprocess_test.cc ++++ b/base/test/multiprocess_test.cc +@@ -12,7 +12,7 @@ + + namespace base { + +-#if !defined(OS_ANDROID) ++#if !defined(OS_ANDROID) && !defined(__ANDROID__) && !defined(__ANDROID_HOST__) + SpawnChildResult SpawnMultiProcessTestChild( + const std::string& procname, + const CommandLine& base_command_line, +@@ -41,7 +41,7 @@ bool TerminateMultiProcessTestChild(cons + return process.Terminate(exit_code, wait); + } + +-#endif // !defined(OS_ANDROID) ++#endif // !OS_ANDROID && !__ANDROID__ && !__ANDROID_HOST__ + + CommandLine GetMultiProcessTestChildBaseCommandLine() { + CommandLine cmd_line = *CommandLine::ForCurrentProcess(); +@@ -54,6 +54,8 @@ CommandLine GetMultiProcessTestChildBase + MultiProcessTest::MultiProcessTest() { + } + ++// Don't compile on ARC. ++#if 0 + SpawnChildResult MultiProcessTest::SpawnChild(const std::string& procname) { + LaunchOptions options; + #if defined(OS_WIN) +@@ -67,6 +69,7 @@ SpawnChildResult MultiProcessTest::Spawn + const LaunchOptions& options) { + return SpawnMultiProcessTestChild(procname, MakeCmdLine(procname), options); + } ++#endif + + CommandLine MultiProcessTest::MakeCmdLine(const std::string& procname) { + CommandLine command_line = GetMultiProcessTestChildBaseCommandLine(); diff --git a/libchrome_tools/patch/symbolize.patch b/libchrome_tools/patch/symbolize.patch new file mode 100644 index 0000000000000000000000000000000000000000..a7eeda516245e7bf79612e6f8a9021892bba6a94 --- /dev/null +++ b/libchrome_tools/patch/symbolize.patch @@ -0,0 +1,18 @@ +--- /dev/null ++++ b/base/third_party/symbolize/symbolize.h +@@ -0,0 +1,15 @@ ++// Copyright (C) 2018 The Android Open Source Project ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++ ++#error "symbolize support was removed from libchrome" diff --git a/libchrome_tools/patch/task_scheduler.patch b/libchrome_tools/patch/task_scheduler.patch new file mode 100644 index 0000000000000000000000000000000000000000..39a4ead16da8405467f49d6dd378c8a20fb3c063 --- /dev/null +++ b/libchrome_tools/patch/task_scheduler.patch @@ -0,0 +1,121 @@ +# libchrome does not support TaskScheduler. + +--- a/base/threading/sequenced_worker_pool.cc ++++ b/base/threading/sequenced_worker_pool.cc +@@ -27,8 +27,12 @@ + #include "base/strings/stringprintf.h" + #include "base/synchronization/condition_variable.h" + #include "base/synchronization/lock.h" ++// Don't enable the redirect to TaskScheduler on Arc++ to avoid pulling a bunch ++// of dependencies. Some code also #ifdef'ed below. ++#if 0 + #include "base/task_scheduler/post_task.h" + #include "base/task_scheduler/task_scheduler.h" ++#endif + #include "base/threading/platform_thread.h" + #include "base/threading/sequenced_task_runner_handle.h" + #include "base/threading/simple_thread.h" +@@ -755,10 +759,13 @@ bool SequencedWorkerPool::Inner::PostTas + if (optional_token_name) + sequenced.sequence_token_id = LockedGetNamedTokenID(*optional_token_name); + ++ // See on top of the file why we don't compile this on Arc++. ++#if 0 + if (g_all_pools_state == AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER) { + if (!PostTaskToTaskScheduler(std::move(sequenced), delay)) + return false; + } else { ++#endif + SequencedWorkerPool::WorkerShutdown shutdown_behavior = + sequenced.shutdown_behavior; + pending_tasks_.insert(std::move(sequenced)); +@@ -767,7 +774,9 @@ bool SequencedWorkerPool::Inner::PostTas + blocking_shutdown_pending_task_count_++; + + create_thread_id = PrepareToStartAdditionalThreadIfHelpful(); ++#if 0 + } ++#endif + } + + // Use != REDIRECTED_TO_TASK_SCHEDULER instead of == USE_WORKER_POOL to ensure +@@ -802,6 +811,10 @@ bool SequencedWorkerPool::Inner::PostTas + bool SequencedWorkerPool::Inner::PostTaskToTaskScheduler( + SequencedTask sequenced, + const TimeDelta& delay) { ++#if 1 ++ NOTREACHED(); ++ return false; ++#else + DCHECK_EQ(AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER, g_all_pools_state); + + lock_.AssertAcquired(); +@@ -832,12 +845,17 @@ bool SequencedWorkerPool::Inner::PostTas + return GetTaskSchedulerTaskRunner(sequenced.sequence_token_id, traits) + ->PostDelayedTask(sequenced.posted_from, std::move(sequenced.task), + delay); ++#endif + } + + scoped_refptr + SequencedWorkerPool::Inner::GetTaskSchedulerTaskRunner( + int sequence_token_id, + const TaskTraits& traits) { ++#if 1 ++ NOTREACHED(); ++ return scoped_refptr(); ++#else + DCHECK_EQ(AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER, g_all_pools_state); + + lock_.AssertAcquired(); +@@ -871,16 +889,19 @@ SequencedWorkerPool::Inner::GetTaskSched + } + + return task_runner; ++#endif + } + + bool SequencedWorkerPool::Inner::RunsTasksOnCurrentThread() const { + AutoLock lock(lock_); + if (g_all_pools_state == AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER) { ++#if 0 + if (!runs_tasks_on_verifier_) { + runs_tasks_on_verifier_ = CreateTaskRunnerWithTraits( + TaskTraits().MayBlock().WithBaseSyncPrimitives().WithPriority( + task_priority_)); + } ++#endif + return runs_tasks_on_verifier_->RunsTasksOnCurrentThread(); + } else { + return ContainsKey(threads_, PlatformThread::CurrentId()); +@@ -1467,6 +1488,9 @@ void SequencedWorkerPool::EnableForProce + // static + void SequencedWorkerPool::EnableWithRedirectionToTaskSchedulerForProcess( + TaskPriority max_task_priority) { ++#if 1 ++ NOTREACHED(); ++#else + // TODO(fdoray): Uncomment this line. It is initially commented to avoid a + // revert of the CL that adds debug::DumpWithoutCrashing() in case of + // waterfall failures. +@@ -1474,6 +1498,7 @@ void SequencedWorkerPool::EnableWithRedi + DCHECK(TaskScheduler::GetInstance()); + g_all_pools_state = AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER; + g_max_task_priority = max_task_priority; ++#endif + } + + // static +@@ -1623,8 +1648,12 @@ void SequencedWorkerPool::FlushForTestin + DCHECK(!RunsTasksOnCurrentThread()); + base::ThreadRestrictions::ScopedAllowWait allow_wait; + if (g_all_pools_state == AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER) { ++#if 1 ++ NOTREACHED(); ++#else + // TODO(gab): Remove this if http://crbug.com/622400 fails. + TaskScheduler::GetInstance()->FlushForTesting(); ++#endif + } else { + inner_->CleanupForTesting(); + } diff --git a/libchrome_tools/patch/time.patch b/libchrome_tools/patch/time.patch new file mode 100644 index 0000000000000000000000000000000000000000..496e68f305010f77b0eff6bd3fe32c2f01e496aa --- /dev/null +++ b/libchrome_tools/patch/time.patch @@ -0,0 +1,19 @@ +# Cherry-pick from r488841. + +--- a/base/time/time.h ++++ b/base/time/time.h +@@ -341,6 +341,14 @@ class TimeBase { + // provided operators. + int64_t ToInternalValue() const { return us_; } + ++ // The amount of time since the origin (or "zero") point. This is a syntactic ++ // convenience to aid in code readability, mainly for debugging/testing use ++ // cases. ++ // ++ // Warning: While the Time subclass has a fixed origin point, the origin for ++ // the other subclasses can vary each time the application is restarted. ++ TimeDelta since_origin() const { return TimeDelta::FromMicroseconds(us_); } ++ + TimeClass& operator=(TimeClass other) { + us_ = other.us_; + return *(static_cast(this)); diff --git a/libchrome_tools/patch/trace_event.patch b/libchrome_tools/patch/trace_event.patch new file mode 100644 index 0000000000000000000000000000000000000000..514ef53bbc47d2d9de238aa4f5b8c4d032e2db14 --- /dev/null +++ b/libchrome_tools/patch/trace_event.patch @@ -0,0 +1,191 @@ +--- a/base/trace_event/trace_event.h ++++ b/base/trace_event/trace_event.h +@@ -5,6 +5,43 @@ + #ifndef BASE_TRACE_EVENT_TRACE_EVENT_H_ + #define BASE_TRACE_EVENT_TRACE_EVENT_H_ + ++// Replace with stub implementation. ++#if 1 ++#include "base/trace_event/common/trace_event_common.h" ++#include "base/trace_event/heap_profiler.h" ++ ++// To avoid -Wunused-* errors, eat expression by macro. ++namespace libchrome_internal { ++template void Ignore(Args&&... args) {} ++} ++#define INTERNAL_IGNORE(...) \ ++ (false ? libchrome_internal::Ignore(__VA_ARGS__) : (void) 0) ++ ++// Body is effectively empty. ++#define INTERNAL_TRACE_EVENT_ADD_SCOPED(...) INTERNAL_IGNORE(__VA_ARGS__) ++#define INTERNAL_TRACE_TASK_EXECUTION(...) ++#define INTERNAL_TRACE_EVENT_ADD_SCOPED_WITH_FLOW(...) \ ++ INTERNAL_IGNORE(__VA_ARGS__) ++#define TRACE_ID_MANGLE(val) (val) ++ ++namespace base { ++namespace trace_event { ++ ++class TraceLog { ++ public: ++ static TraceLog* GetInstance() { ++ static TraceLog instance; ++ return &instance; ++ } ++ ++ pid_t process_id() { return 0; } ++ void SetCurrentThreadBlocksMessageLoop() {} ++}; ++ ++} // namespace trace_event ++} // namespace base ++#else ++ + // This header file defines implementation details of how the trace macros in + // trace_event_common.h collect and store trace events. Anything not + // implementation-specific should go in trace_event_common.h instead of here. +@@ -1115,4 +1152,5 @@ template class TraceSco + } // namespace trace_event + } // namespace base + ++#endif + #endif // BASE_TRACE_EVENT_TRACE_EVENT_H_ +--- a/base/trace_event/heap_profiler.h ++++ b/base/trace_event/heap_profiler.h +@@ -5,6 +5,22 @@ + #ifndef BASE_TRACE_EVENT_HEAP_PROFILER_H + #define BASE_TRACE_EVENT_HEAP_PROFILER_H + ++// Replace with stub implementation. ++#if 1 ++#define TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION \ ++ trace_event_internal::HeapProfilerScopedTaskExecutionTracker ++ ++namespace trace_event_internal { ++ ++class HeapProfilerScopedTaskExecutionTracker { ++ public: ++ explicit HeapProfilerScopedTaskExecutionTracker(const char*) {} ++}; ++ ++} // namespace trace_event_internal ++ ++#else ++ + #include "base/compiler_specific.h" + #include "base/trace_event/heap_profiler_allocation_context_tracker.h" + +@@ -86,4 +102,5 @@ class BASE_EXPORT HeapProfilerScopedIgno + + } // namespace trace_event_internal + ++#endif + #endif // BASE_TRACE_EVENT_HEAP_PROFILER_H +--- a/base/test/test_pending_task.h ++++ b/base/test/test_pending_task.h +@@ -10,7 +10,8 @@ + #include "base/callback.h" + #include "base/location.h" + #include "base/time/time.h" +-#include "base/trace_event/trace_event_argument.h" ++// Unsupported in libchrome. ++// #include "base/trace_event/trace_event_argument.h" + + namespace base { + +@@ -58,10 +59,13 @@ struct TestPendingTask { + TimeDelta delay; + TestNestability nestability; + ++// Unsupported in libchrome. ++#if 0 + // Functions for using test pending task with tracing, useful in unit + // testing. + void AsValueInto(base::trace_event::TracedValue* state) const; + std::unique_ptr AsValue() const; ++#endif + std::string ToString() const; + + private: +--- a/base/test/test_pending_task.cc ++++ b/base/test/test_pending_task.cc +@@ -38,6 +38,8 @@ bool TestPendingTask::ShouldRunBefore(co + + TestPendingTask::~TestPendingTask() {} + ++// Unsupported in libchrome. ++#if 0 + void TestPendingTask::AsValueInto(base::trace_event::TracedValue* state) const { + state->SetInteger("run_at", GetTimeToRun().ToInternalValue()); + state->SetString("posting_function", location.ToString()); +@@ -61,10 +63,14 @@ TestPendingTask::AsValue() const { + AsValueInto(state.get()); + return std::move(state); + } ++#endif + + std::string TestPendingTask::ToString() const { + std::string output("TestPendingTask("); ++// Unsupported in libchrome. ++#if 0 + AsValue()->AppendAsTraceFormat(&output); ++#endif + output += ")"; + return output; + } +--- a/base/threading/thread_id_name_manager.cc ++++ b/base/threading/thread_id_name_manager.cc +@@ -10,7 +10,8 @@ + #include "base/logging.h" + #include "base/memory/singleton.h" + #include "base/strings/string_util.h" +-#include "base/trace_event/heap_profiler_allocation_context_tracker.h" ++// Unsupported in libchrome. ++// #include "base/trace_event/heap_profiler_allocation_context_tracker.h" + + namespace base { + namespace { +@@ -80,8 +81,9 @@ void ThreadIdNameManager::SetName(Platfo + // call GetName(which holds a lock) during the first allocation because it can + // cause a deadlock when the first allocation happens in the + // ThreadIdNameManager itself when holding the lock. +- trace_event::AllocationContextTracker::SetCurrentThreadName( +- leaked_str->c_str()); ++ // Unsupported in libchrome. ++ // trace_event::AllocationContextTracker::SetCurrentThreadName( ++ // leaked_str->c_str()); + } + + const char* ThreadIdNameManager::GetName(PlatformThreadId id) { +--- a/base/memory/shared_memory_posix.cc ++++ b/base/memory/shared_memory_posix.cc +@@ -15,7 +15,8 @@ + #include "base/files/scoped_file.h" + #include "base/logging.h" + #include "base/memory/shared_memory_helper.h" +-#include "base/memory/shared_memory_tracker.h" ++// Unsupported in libchrome. ++// #include "base/memory/shared_memory_tracker.h" + #include "base/posix/eintr_wrapper.h" + #include "base/posix/safe_strerror.h" + #include "base/process/process_metrics.h" +@@ -288,7 +291,8 @@ bool SharedMemory::MapAt(off_t offset, s + DCHECK_EQ(0U, + reinterpret_cast(memory_) & + (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); +- SharedMemoryTracker::GetInstance()->IncrementMemoryUsage(*this); ++ // Unsupported in libchrome. ++ // SharedMemoryTracker::GetInstance()->IncrementMemoryUsage(*this); + } else { + memory_ = NULL; + } +@@ -301,7 +305,8 @@ bool SharedMemory::Unmap() { + return false; + + munmap(memory_, mapped_size_); +- SharedMemoryTracker::GetInstance()->DecrementMemoryUsage(*this); ++ // Unsupported in libchrome. ++ // SharedMemoryTracker::GetInstance()->DecrementMemoryUsage(*this); + memory_ = NULL; + mapped_size_ = 0; + return true; diff --git a/libchrome_tools/patch/virtual_destructor.patch b/libchrome_tools/patch/virtual_destructor.patch new file mode 100644 index 0000000000000000000000000000000000000000..64111333ead409831365d8fe280b7688696af1af --- /dev/null +++ b/libchrome_tools/patch/virtual_destructor.patch @@ -0,0 +1,78 @@ +--- a/base/metrics/histogram.cc ++++ b/base/metrics/histogram.cc +@@ -94,6 +94,7 @@ class Histogram::Factory { + uint32_t bucket_count, + int32_t flags) + : Factory(name, HISTOGRAM, minimum, maximum, bucket_count, flags) {} ++ virtual ~Factory() = default; + + // Create histogram based on construction parameters. Caller takes + // ownership of the returned object. +@@ -741,6 +742,7 @@ class LinearHistogram::Factory : public + bucket_count, flags) { + descriptions_ = descriptions; + } ++ ~Factory() override = default; + + protected: + BucketRanges* CreateRanges() override { +@@ -932,6 +934,7 @@ class BooleanHistogram::Factory : public + public: + Factory(const std::string& name, int32_t flags) + : Histogram::Factory(name, BOOLEAN_HISTOGRAM, 1, 2, 3, flags) {} ++ ~Factory() override = default; + + protected: + BucketRanges* CreateRanges() override { +@@ -1020,6 +1023,7 @@ class CustomHistogram::Factory : public + : Histogram::Factory(name, CUSTOM_HISTOGRAM, 0, 0, 0, flags) { + custom_ranges_ = custom_ranges; + } ++ ~Factory() override = default; + + protected: + BucketRanges* CreateRanges() override { +--- a/base/metrics/statistics_recorder.h ++++ b/base/metrics/statistics_recorder.h +@@ -67,6 +67,7 @@ class BASE_EXPORT StatisticsRecorder { + // histograms from providers when necessary. + class HistogramProvider { + public: ++ virtual ~HistogramProvider() {} + // Merges all histogram information into the global versions. + virtual void MergeHistogramDeltas() = 0; + }; +--- a/base/bind_unittest.cc ++++ b/base/bind_unittest.cc +@@ -69,6 +69,7 @@ static const int kChildValue = 2; + + class Parent { + public: ++ virtual ~Parent() {} + void AddRef() const {} + void Release() const {} + virtual void VirtualSet() { value = kParentValue; } +@@ -78,18 +79,23 @@ class Parent { + + class Child : public Parent { + public: ++ ~Child() override {} + void VirtualSet() override { value = kChildValue; } + void NonVirtualSet() { value = kChildValue; } + }; + + class NoRefParent { + public: ++ virtual ~NoRefParent() {} + virtual void VirtualSet() { value = kParentValue; } + void NonVirtualSet() { value = kParentValue; } + int value; + }; + + class NoRefChild : public NoRefParent { ++ public: ++ ~NoRefChild() override {} ++ private: + void VirtualSet() override { value = kChildValue; } + void NonVirtualSet() { value = kChildValue; } + }; diff --git a/libchrome_tools/update_libchrome.py b/libchrome_tools/update_libchrome.py new file mode 100644 index 0000000000000000000000000000000000000000..e6155a07d3483847191521839b52c4175555a702 --- /dev/null +++ b/libchrome_tools/update_libchrome.py @@ -0,0 +1,206 @@ +#!/usr/bin/python3 + +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Updates libchrome. + +This script uprevs the libchrome library with newer Chromium code. +How to use: + +Prepare your local Chromium repository with the target revision. +$ cd external/libchrome +$ python3 libchrome_tools/update_libchrome.py \ + --chromium_root=${PATH_TO_YOUR_LOCAL_CHROMIUM_REPO} + +This script does following things; +- Clean existing libchrome code, except some manually created files and tools. +- Copy necessary files from original Chromium repository. +- Apply patches to the copied files, if necessary. +""" + + +import argparse +import fnmatch +import glob +import os +import re +import shutil +import subprocess + + +_TOOLS_DIR = os.path.dirname(os.path.realpath(__file__)) +_LIBCHROME_ROOT = os.path.dirname(_TOOLS_DIR) + + +# Files in this list or in the directory listed here will be just copied. +# Paths ends with '/' is interpreted as directory. +_IMPORT_LIST = [ + 'mojo/', + 'third_party/catapult/LICENSE', + 'third_party/catapult/devil/', + 'third_party/ply/', + 'third_party/markupsafe/', + 'third_party/jinja2/', +] + +# Files which are in the repository, but should not be imported from Chrome +# repository. +_IMPORT_BLACKLIST = [ + # Libchrome specific files. + '.gitignore', + 'Android.bp', + 'MODULE_LICENSE_BSD', + 'NOTICE', + 'OWNERS', + 'SConstruct', + 'libmojo.pc.in', + 'testrunner.cc', + + # No Chromium OWNERS should be imported. + '*/OWNERS', + + # libchrome_tools is out of the update target. + 'libchrome_tools/*', + + # No internal directories. + 'mojo/internal/*', + + # Those files should be generated. Please see also buildflag_header.patch. + 'base/allocator/features.h', + 'base/debug/debugging_flags.h', + 'gen/*', + + # Blacklist several third party libraries; system libraries should be used. + 'base/third_party/libevent/*', + 'base/third_party/symbolize/*', + 'testing/gmock/*', + 'testing/gtest/*', + 'third_party/ashmem/*', + 'third_party/modp_b64/*', + 'third_party/protobuf/*', +] + +def _find_target_files(chromium_root): + """Returns target files to be upreved.""" + # Files in the repository should be updated. + output = subprocess.check_output( + ['git', 'ls-tree', '-r', '--name-only', '--full-name', 'HEAD'], + cwd=_LIBCHROME_ROOT).decode('utf-8') + + # Files in _IMPORT_LIST are copied in the following section, so + # exclude them from candidates, here, so that files deleted in chromium + # repository will be deleted on update. + candidates = [ + path for path in output.splitlines() + if not any(path.startswith(import_path) for import_path in _IMPORT_LIST)] + + # All files listed in _IMPORT_LIST should be imported, too. + for import_path in _IMPORT_LIST: + import_root = os.path.join(chromium_root, import_path) + + # If it is a file, just add to the candidates. + if os.path.isfile(import_root): + candidates.append(import_path) + continue + + # If it is a directory, traverse all files in the directory recursively + # and add all of them to candidates. + for dirpath, dirnames, filenames in os.walk(import_root): + for filename in filenames: + filepath = os.path.join(dirpath, filename) + candidates.append(os.path.relpath(filepath, chromium_root)) + + # Apply blacklist. + exclude_pattern = re.compile('|'.join( + '(?:%s)' % fnmatch.translate(pattern) for pattern in _IMPORT_BLACKLIST)) + return [filepath for filepath in candidates + if not exclude_pattern.match(filepath)] + + +def _clean_existing_dir(output_root): + """Removes existing libchrome files. + + Args: + output_root: Path to the output directory. + """ + os.makedirs(output_root, mode=0o755, exist_ok=True) + for path in os.listdir(output_root): + target_path = os.path.join(output_root, path) + if (not os.path.isdir(target_path) or path in ('.git', 'libchrome_tools')): + continue + shutil.rmtree(target_path) + + +def _import_files(chromium_root, output_root): + """Copies files from Chromium repository into libchrome. + + Args: + chromium_root: Path to the Chromium's repository. + output_root: Path to the output directory. + """ + for filepath in _find_target_files(chromium_root): + source_path = os.path.join(chromium_root, filepath) + target_path = os.path.join(output_root, filepath) + os.makedirs(os.path.dirname(target_path), mode=0o755, exist_ok=True) + shutil.copy2(source_path, target_path) + + +def _apply_patch_files(patch_root, output_root): + """Applies patches. + + libchrome needs some modification from Chromium repository, e.g. supporting + toolchain which is not used by Chrome, or using system library rather than + the library checked in the Chromium repository. + See each *.patch file in libchrome_tools/patch/ directory for details. + + Args: + patch_root: Path to the directory containing patch files. + output_root: Path to the output directory. + """ + for patch_file in glob.iglob(os.path.join(patch_root, '*.patch')): + with open(patch_file, 'r') as f: + subprocess.check_call(['patch', '-p1'], stdin=f, cwd=output_root) + + +def _parse_args(): + """Parses commandline arguments.""" + parser = argparse.ArgumentParser() + + # TODO(hidehiko): Support to specify the Chromium's revision number. + parser.add_argument( + '--chromium_root', + help='Root directory to the local chromium repository.') + parser.add_argument( + '--output_root', + default=_LIBCHROME_ROOT, + help='Output directory, which is libchrome root directory.') + parser.add_argument( + '--patch_dir', + default=os.path.join(_TOOLS_DIR, 'patch'), + help='Directory containing patch files to be applied.') + + return parser.parse_args() + + +def main(): + args = _parse_args() + _clean_existing_dir(args.output_root) + _import_files(args.chromium_root, args.output_root) + _apply_patch_files(args.patch_dir, args.output_root) + # TODO(hidehiko): Create a git commit with filling templated message. + + +if __name__ == '__main__': + main() diff --git a/libmojo.pc.in b/libmojo.pc.in new file mode 100644 index 0000000000000000000000000000000000000000..a750bddddc5d58c68d85984fa0075ec93578ed10 --- /dev/null +++ b/libmojo.pc.in @@ -0,0 +1,13 @@ +bslot=@BSLOT@ +prefix=/usr +exec_prefix=${prefix} +libdir=${exec_prefix}/@LIB@ +includedir=${prefix}/include + +Name: libmojo +Description: Chrome Mojo IPC library +Requires.private: +Version: ${bslot} +Libs: -lmojo-${bslot}.pic +Libs.private: +Cflags: -I${includedir}/libmojo-${bslot} -Wno-cast-qual -Wno-cast-align diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..070e2d1c31efd229093f63333cc36181a801a73f --- /dev/null +++ b/mojo/BUILD.gn @@ -0,0 +1,41 @@ +# Copyright 2014 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/ui.gni") + +group("mojo") { + # Meta-target, don't link into production code. + testonly = true + deps = [ + ":tests", + "//mojo/common", + ] + + if (!(is_linux && current_cpu == "x86")) { + deps += [ "//mojo/public" ] + } + + if (is_android) { + deps += [ "//mojo/android" ] + } + + deps += [ "//services/service_manager:all" ] +} + +group("tests") { + testonly = true + deps = [ + "//ipc:ipc_tests", + "//mojo/common:mojo_common_unittests", + "//mojo/edk/js/tests", + "//mojo/edk/system:mojo_message_pipe_perftests", + "//mojo/edk/system:mojo_system_unittests", + "//mojo/edk/test:mojo_public_bindings_perftests", + "//mojo/edk/test:mojo_public_bindings_unittests", + "//mojo/edk/test:mojo_public_system_perftests", + "//mojo/edk/test:mojo_public_system_unittests", + "//services/service_manager/public/cpp/tests:mojo_public_application_unittests", + "//services/service_manager/tests", + ] +} diff --git a/mojo/DEPS b/mojo/DEPS new file mode 100644 index 0000000000000000000000000000000000000000..49d7fd30d6241ed61f6a804f37b30f794b075153 --- /dev/null +++ b/mojo/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+base", + "+build", + "+testing", + + "+services/service_manager", +] diff --git a/mojo/PRESUBMIT.py b/mojo/PRESUBMIT.py new file mode 100644 index 0000000000000000000000000000000000000000..2766055a5dbd5775b7da58e9a56c14f47e76287f --- /dev/null +++ b/mojo/PRESUBMIT.py @@ -0,0 +1,44 @@ +# Copyright 2014 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. + +"""Presubmit script for mojo + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details about the presubmit API built into depot_tools. +""" + +import os.path + +def CheckChangeOnUpload(input_api, output_api): + # Additional python module paths (we're in src/mojo/); not everyone needs + # them, but it's easiest to add them to everyone's path. + # For ply and jinja2: + third_party_path = os.path.join( + input_api.PresubmitLocalPath(), "..", "third_party") + # For the bindings generator: + mojo_public_bindings_pylib_path = os.path.join( + input_api.PresubmitLocalPath(), "public", "tools", "bindings", "pylib") + # For the python bindings: + mojo_python_bindings_path = os.path.join( + input_api.PresubmitLocalPath(), "public", "python") + # TODO(vtl): Don't lint these files until the (many) problems are fixed + # (possibly by deleting/rewriting some files). + temporary_black_list = input_api.DEFAULT_BLACK_LIST + \ + (r".*\bpublic[\\\/]tools[\\\/]bindings[\\\/]pylib[\\\/]mojom[\\\/]" + r"generate[\\\/].+\.py$", + r".*\bpublic[\\\/]tools[\\\/]bindings[\\\/]generators[\\\/].+\.py$", + r".*\bspy[\\\/]ui[\\\/].+\.py$", + r".*\btools[\\\/]pylib[\\\/]transitive_hash\.py$", + r".*\btools[\\\/]test_runner\.py$") + + results = [] + pylint_extra_paths = [ + third_party_path, + mojo_public_bindings_pylib_path, + mojo_python_bindings_path, + ] + results += input_api.canned_checks.RunPylint( + input_api, output_api, extra_paths_list=pylint_extra_paths, + black_list=temporary_black_list) + return results diff --git a/mojo/README.md b/mojo/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e1e7583fc83722170d0f30c5028658a035fcf64d --- /dev/null +++ b/mojo/README.md @@ -0,0 +1,142 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Getting Started With Mojo + +To get started using Mojo in applications which already support it (such as +Chrome), the fastest path forward will be to look at the bindings documentation +for your language of choice ([**C++**](#C_Bindings), +[**JavaScript**](#JavaScript-Bindings), or [**Java**](#Java-Bindings)) as well +as the documentation for the +[**Mojom IDL and bindings generator**](/mojo/public/tools/bindings). + +If you're looking for information on creating and/or connecting to services, see +the top-level [Services documentation](/services). + +For specific details regarding the conversion of old things to new things, check +out [Converting Legacy Chrome IPC To Mojo](/ipc). + +## System Overview + +Mojo is a layered collection of runtime libraries providing a platform-agnostic +abstraction of common IPC primitives, a message IDL format, and a bindings +library with code generation for multiple target languages to facilitate +convenient message passing across arbitrary inter- and intra-process boundaries. + +The documentation here is segmented according to the different isolated layers +and libraries comprising the system. The basic hierarchy of features is as +follows: + +![Mojo Library Layering: EDK on bottom, different language bindings on top, public system support APIs in the middle](https://docs.google.com/drawings/d/1aNbLfF-fejgzxCxH_b8xAaCVvftW8BGTH_EHD7nvU1w/pub?w=570&h=327) + +## Embedder Development Kit (EDK) +Every process to be interconnected via Mojo IPC is called a **Mojo embedder** +and needs to embed the +[**Embedder Development Kit (EDK)**](/mojo/edk/embedder) library. The EDK +exposes the means for an embedder to physically connect one process to another +using any supported native IPC primitive (*e.g.,* a UNIX domain socket or +Windows named pipe) on the host platform. + +Details regarding where and how an application process actually embeds and +configures the EDK are generaly hidden from the rest of the application code, +and applications instead use the public System and Bindings APIs to get things +done within processes that embed Mojo. + +## C System API +Once the EDK is initialized within a process, the public +[**C System API**](/mojo/public/c/system) is usable on any thread for the +remainder of the process's lifetime. This is a lightweight API with a relatively +small (and eventually stable) ABI. Typically this API is not used directly, but +it is the foundation upon which all remaining upper layers are built. It exposes +the fundamental capabilities to create and interact with various types of Mojo +handles including **message pipes**, **data pipes**, and **shared buffers**. + +## High-Level System APIs + +There is a relatively small, higher-level system API for each supported +language, built upon the low-level C API. Like the C API, direct usage of these +system APIs is rare compared to the bindings APIs, but it is sometimes desirable +or necessary. + +### C++ +The [**C++ System API**](/mojo/public/cpp/system) provides a layer of +C++ helper classes and functions to make safe System API usage easier: +strongly-typed handle scopers, synchronous waiting operations, system handle +wrapping and unwrapping helpers, common handle operations, and utilities for +more easily watching handle state changes. + +### JavaScript +The [**JavaScript APIs**](/mojo/public/js) are WIP. :) + +### Java +The [**Java System API**](/mojo/public/java/system) provides helper classes for +working with Mojo primitives, covering all basic functionality of the low-level +C API. + +## High-Level Bindings APIs +Typically developers do not use raw message pipe I/O directly, but instead +define some set of interfaces which are used to generate code that message pipe +usage feel like a more idiomatic method-calling interface in the target +language of choice. This is the bindings layer. + +### Mojom IDL and Bindings Generator +Interfaces are defined using the [**Mojom IDL**](/mojo/public/tools/bindings), +which can be fed to the [**bindings generator**](/mojo/public/tools/bindings) to +generate code in various supported languages. Generated code manages +serialization and deserialization of messages between interface clients and +implementations, simplifying the code -- and ultimately hiding the message pipe +-- on either side of an interface connection. + +### C++ Bindings +By far the most commonly used API defined by Mojo, the +[**C++ Bindings API**](/mojo/public/cpp/bindings) exposes a robust set of +features for interacting with message pipes via generated C++ bindings code, +including support for sets of related bindings endpoints, associated interfaces, +nested sync IPC, versioning, bad-message reporting, arbitrary message filter +injection, and convenient test facilities. + +### JavaScript Bindings +The [**JavaScript APIs**](/mojo/public/js) are WIP. :) + +### Java Bindings +The [**Java Bindings API**](/mojo/public/java/bindings) provides helper classes +for working with Java code emitted by the bindings generator. + +## FAQ + +### Why not protobuf? Why a new thing? +There are number of potentially decent answers to this question, but the +deal-breaker is that a useful IPC mechanism must support transfer of native +object handles (*e.g.* file descriptors) across process boundaries. Other +non-new IPC things that do support this capability (*e.g.* D-Bus) have their own +substantial deficiencies. + +### Are message pipes expensive? +No. As an implementation detail, creating a message pipe is essentially +generating two random numbers and stuffing them into a hash table, along with a +few tiny heap allocations. + +### So really, can I create like, thousands of them? +Yes! Nobody will mind. Create millions if you like. (OK but maybe don't.) + +### Can I use in-process message pipes? +Yes, and message pipe usage is identical regardless of whether the pipe actually +crosses a process boundary -- in fact this detail is intentionally obscured. + +Message pipes which don't cross a process boundary are efficient: sent messages +are never copied, and a write on one end will synchronously modify the message +queue on the other end. When working with generated C++ bindings, for example, +the net result is that an `InterfacePtr` on one thread sending a message to a +`Binding` on another thread (or even the same thread) is effectively a +`PostTask` to the `Binding`'s `TaskRunner` with the added -- but often small -- +costs of serialization, deserialization, validation, and some internal routing +logic. + +### What about ____? + +Please post questions to +[`chromium-mojo@chromium.org`](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-mojo)! +The list is quite responsive. + diff --git a/mojo/android/BUILD.gn b/mojo/android/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..1a8cdbdb3c76092a610d0131e99cf45f7d834095 --- /dev/null +++ b/mojo/android/BUILD.gn @@ -0,0 +1,155 @@ +# Copyright 2014 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") + +group("android") { + testonly = true + deps = [ + ":mojo_javatests", + ":mojo_test_apk", + ":system_java", + ] +} + +generate_jni("jni_headers") { + sources = [ + "javatests/src/org/chromium/mojo/MojoTestCase.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java", + ] + public_deps = [ + ":system_java_jni_headers", + ] + + jni_package = "mojo" +} + +generate_jni("system_java_jni_headers") { + sources = [ + "system/src/org/chromium/mojo/system/impl/BaseRunLoop.java", + "system/src/org/chromium/mojo/system/impl/CoreImpl.java", + "system/src/org/chromium/mojo/system/impl/WatcherImpl.java", + ] + + jni_package = "mojo" +} + +source_set("libsystem_java") { + sources = [ + "system/base_run_loop.cc", + "system/base_run_loop.h", + "system/core_impl.cc", + "system/core_impl.h", + "system/watcher_impl.cc", + "system/watcher_impl.h", + ] + + deps = [ + ":system_java_jni_headers", + "//base", + "//mojo/public/c/system", + "//mojo/public/cpp/system", + ] +} + +android_library("system_java") { + java_files = [ + "system/src/org/chromium/mojo/system/impl/BaseRunLoop.java", + "system/src/org/chromium/mojo/system/impl/CoreImpl.java", + "system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/HandleBase.java", + "system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/WatcherImpl.java", + ] + + deps = [ + "//base:base_java", + "//mojo/public/java:system_java", + ] +} + +android_library("mojo_javatests") { + testonly = true + java_files = [ + "javatests/src/org/chromium/mojo/HandleMock.java", + "javatests/src/org/chromium/mojo/MojoTestCase.java", + "javatests/src/org/chromium/mojo/TestUtils.java", + "javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java", + "javatests/src/org/chromium/mojo/bindings/BindingsTest.java", + "javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java", + "javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java", + "javatests/src/org/chromium/mojo/bindings/CallbacksTest.java", + "javatests/src/org/chromium/mojo/bindings/ConnectorTest.java", + "javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java", + "javatests/src/org/chromium/mojo/bindings/InterfacesTest.java", + "javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java", + "javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java", + "javatests/src/org/chromium/mojo/bindings/RouterTest.java", + "javatests/src/org/chromium/mojo/bindings/SerializationTest.java", + "javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTest.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java", + "javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java", + "javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java", + ] + + deps = [ + ":system_java", + "//base:base_java", + "//base:base_java_test_support", + "//mojo/public/interfaces/bindings/tests:test_interfaces_java", + "//mojo/public/interfaces/bindings/tests:test_mojom_import2_java", + "//mojo/public/interfaces/bindings/tests:test_mojom_import_java", + "//mojo/public/java:bindings_java", + "//mojo/public/java:system_java", + "//third_party/android_support_test_runner:runner_java", + ] + + data = [ + "//mojo/public/interfaces/bindings/tests/data/validation/", + ] +} + +shared_library("mojo_java_unittests") { + testonly = true + + sources = [ + "javatests/init_library.cc", + "javatests/mojo_test_case.cc", + "javatests/mojo_test_case.h", + "javatests/validation_test_util.cc", + "javatests/validation_test_util.h", + ] + + deps = [ + ":jni_headers", + ":libsystem_java", + ":system_java_jni_headers", + "//base", + "//base/test:test_support", + "//build/config/sanitizers:deps", + "//mojo/edk/system", + "//mojo/public/cpp/bindings/tests:mojo_public_bindings_test_utils", + "//mojo/public/cpp/test_support:test_utils", + ] + defines = [ "UNIT_TEST" ] +} + +instrumentation_test_apk("mojo_test_apk") { + deps = [ + ":mojo_javatests", + ":system_java", + "//base:base_java", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//mojo/public/java:bindings_java", + "//third_party/android_support_test_runner:runner_java", + ] + shared_libraries = [ ":mojo_java_unittests" ] + apk_name = "MojoTest" + android_manifest = "javatests/AndroidManifest.xml" +} diff --git a/mojo/android/DEPS b/mojo/android/DEPS new file mode 100644 index 0000000000000000000000000000000000000000..c80012b5621e39fd8d3706ab0ee35fbea8154c7d --- /dev/null +++ b/mojo/android/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+jni", +] diff --git a/mojo/android/javatests/AndroidManifest.xml b/mojo/android/javatests/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..32a3927662b16f4d7dc9c203a1a6fdf431abe122 --- /dev/null +++ b/mojo/android/javatests/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + diff --git a/mojo/android/javatests/DEPS b/mojo/android/javatests/DEPS new file mode 100644 index 0000000000000000000000000000000000000000..78cf465ca0c058e11f815a48232d2de446f6d93d --- /dev/null +++ b/mojo/android/javatests/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + # out should be allowed by default, but bots are failing on this. + "+out", +] diff --git a/mojo/android/javatests/apk/.empty b/mojo/android/javatests/apk/.empty new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/mojo/android/javatests/init_library.cc b/mojo/android/javatests/init_library.cc new file mode 100644 index 0000000000000000000000000000000000000000..9e1a593011a6b69ec8c6b72acbc8957aaec8f817 --- /dev/null +++ b/mojo/android/javatests/init_library.cc @@ -0,0 +1,50 @@ +// Copyright 2014 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. + +#include "base/android/base_jni_onload.h" +#include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/android/library_loader/library_loader_hooks.h" +#include "base/bind.h" +#include "mojo/android/javatests/mojo_test_case.h" +#include "mojo/android/javatests/validation_test_util.h" +#include "mojo/android/system/core_impl.h" +#include "mojo/android/system/watcher_impl.h" +#include "mojo/edk/embedder/embedder.h" + +namespace { + +base::android::RegistrationMethod kMojoRegisteredMethods[] = { + {"CoreImpl", mojo::android::RegisterCoreImpl}, + {"MojoTestCase", mojo::android::RegisterMojoTestCase}, + {"ValidationTestUtil", mojo::android::RegisterValidationTestUtil}, + {"WatcherImpl", mojo::android::RegisterWatcherImpl}, +}; + +bool RegisterJNI(JNIEnv* env) { + return base::android::RegisterJni(env) && + RegisterNativeMethods(env, kMojoRegisteredMethods, + arraysize(kMojoRegisteredMethods)); +} + +bool NativeInit() { + if (!base::android::OnJNIOnLoadInit()) + return false; + + mojo::edk::Init(); + return true; +} + +} // namespace + +JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + base::android::InitVM(vm); + JNIEnv* env = base::android::AttachCurrentThread(); + if (!base::android::OnJNIOnLoadRegisterJNI(env) || !RegisterJNI(env) || + !NativeInit()) { + return -1; + } + return JNI_VERSION_1_4; +} diff --git a/mojo/android/javatests/mojo_test_case.cc b/mojo/android/javatests/mojo_test_case.cc new file mode 100644 index 0000000000000000000000000000000000000000..fc59009bd40ad7f814985ba6f98be4288a9e5115 --- /dev/null +++ b/mojo/android/javatests/mojo_test_case.cc @@ -0,0 +1,70 @@ +// Copyright 2014 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. + +#include "mojo/android/javatests/mojo_test_case.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/at_exit.h" +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/test/test_support_android.h" +#include "base/threading/thread_task_runner_handle.h" +#include "jni/MojoTestCase_jni.h" + +using base::android::JavaParamRef; + +namespace { + +struct TestEnvironment { + TestEnvironment() {} + + base::ShadowingAtExitManager at_exit; + base::MessageLoop message_loop; +}; + +} // namespace + +namespace mojo { +namespace android { + +static void Init(JNIEnv* env, const JavaParamRef& jcaller) { + base::InitAndroidTestMessageLoop(); +} + +static jlong SetupTestEnvironment(JNIEnv* env, + const JavaParamRef& jcaller) { + return reinterpret_cast(new TestEnvironment()); +} + +static void TearDownTestEnvironment(JNIEnv* env, + const JavaParamRef& jcaller, + jlong test_environment) { + delete reinterpret_cast(test_environment); +} + +static void RunLoop(JNIEnv* env, + const JavaParamRef& jcaller, + jlong timeout_ms) { + base::RunLoop run_loop; + if (timeout_ms) { + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), + base::TimeDelta::FromMilliseconds(timeout_ms)); + run_loop.Run(); + } else { + run_loop.RunUntilIdle(); + } +} + +bool RegisterMojoTestCase(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/javatests/mojo_test_case.h b/mojo/android/javatests/mojo_test_case.h new file mode 100644 index 0000000000000000000000000000000000000000..2ce342848e202a034d95ef7da52d4498b75f5007 --- /dev/null +++ b/mojo/android/javatests/mojo_test_case.h @@ -0,0 +1,20 @@ +// Copyright 2014 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 MOJO_ANDROID_JAVATESTS_CORE_TEST_H_ +#define MOJO_ANDROID_JAVATESTS_CORE_TEST_H_ + +#include + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterMojoTestCase(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_SYSTEM_ANDROID_JAVATESTS_CORE_TEST_H_ diff --git a/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java new file mode 100644 index 0000000000000000000000000000000000000000..1f8de94500a39bca726bf1eed877b5df86eabf36 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java @@ -0,0 +1,226 @@ +// Copyright 2014 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. + +package org.chromium.mojo; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.UntypedHandle; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * A mock handle, that does nothing. + */ +public class HandleMock implements UntypedHandle, MessagePipeHandle, + ProducerHandle, ConsumerHandle, SharedBufferHandle { + + /** + * @see Handle#close() + */ + @Override + public void close() { + // Do nothing. + } + + /** + * @see Handle#querySignalsState() + */ + @Override + public HandleSignalsState querySignalsState() { + return null; + } + + /** + * @see Handle#isValid() + */ + @Override + public boolean isValid() { + return true; + } + + /** + * @see Handle#toUntypedHandle() + */ + @Override + public UntypedHandle toUntypedHandle() { + return this; + } + + /** + * @see org.chromium.mojo.system.Handle#getCore() + */ + @Override + public Core getCore() { + return CoreImpl.getInstance(); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#pass() + */ + @Override + public HandleMock pass() { + return this; + } + + /** + * @see Handle#releaseNativeHandle() + */ + @Override + public int releaseNativeHandle() { + return 0; + } + + /** + * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags) + */ + @Override + public int discardData(int numBytes, DataPipe.ReadFlags flags) { + // Do nothing. + return 0; + } + + /** + * @see ConsumerHandle#readData(java.nio.ByteBuffer, DataPipe.ReadFlags) + */ + @Override + public ResultAnd readData(ByteBuffer elements, DataPipe.ReadFlags flags) { + // Do nothing. + return new ResultAnd(MojoResult.OK, 0); + } + + /** + * @see ConsumerHandle#beginReadData(int, DataPipe.ReadFlags) + */ + @Override + public ByteBuffer beginReadData(int numBytes, + DataPipe.ReadFlags flags) { + // Do nothing. + return null; + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + @Override + public void endReadData(int numBytesRead) { + // Do nothing. + } + + /** + * @see ProducerHandle#writeData(java.nio.ByteBuffer, DataPipe.WriteFlags) + */ + @Override + public ResultAnd writeData(ByteBuffer elements, DataPipe.WriteFlags flags) { + // Do nothing. + return new ResultAnd(MojoResult.OK, 0); + } + + /** + * @see ProducerHandle#beginWriteData(int, DataPipe.WriteFlags) + */ + @Override + public ByteBuffer beginWriteData(int numBytes, + DataPipe.WriteFlags flags) { + // Do nothing. + return null; + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + @Override + public void endWriteData(int numBytesWritten) { + // Do nothing. + } + + /** + * @see MessagePipeHandle#writeMessage(java.nio.ByteBuffer, java.util.List, + * MessagePipeHandle.WriteFlags) + */ + @Override + public void writeMessage(ByteBuffer bytes, List handles, + WriteFlags flags) { + // Do nothing. + } + + /** + * @see MessagePipeHandle#readMessage(java.nio.ByteBuffer, int, MessagePipeHandle.ReadFlags) + */ + @Override + public ResultAnd readMessage( + ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags) { + // Do nothing. + return new ResultAnd(MojoResult.OK, new ReadMessageResult()); + } + + /** + * @see UntypedHandle#toMessagePipeHandle() + */ + @Override + public MessagePipeHandle toMessagePipeHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeConsumerHandle() + */ + @Override + public ConsumerHandle toDataPipeConsumerHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeProducerHandle() + */ + @Override + public ProducerHandle toDataPipeProducerHandle() { + return this; + } + + /** + * @see UntypedHandle#toSharedBufferHandle() + */ + @Override + public SharedBufferHandle toSharedBufferHandle() { + return this; + } + + /** + * @see SharedBufferHandle#duplicate(SharedBufferHandle.DuplicateOptions) + */ + @Override + public SharedBufferHandle duplicate(DuplicateOptions options) { + // Do nothing. + return null; + } + + /** + * @see SharedBufferHandle#map(long, long, SharedBufferHandle.MapFlags) + */ + @Override + public ByteBuffer map(long offset, long numBytes, MapFlags flags) { + // Do nothing. + return null; + } + + /** + * @see SharedBufferHandle#unmap(java.nio.ByteBuffer) + */ + @Override + public void unmap(ByteBuffer buffer) { + // Do nothing. + } + +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java b/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java new file mode 100644 index 0000000000000000000000000000000000000000..f4d7ab72366bb8529b29f71dcff3a9abda739418 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java @@ -0,0 +1,66 @@ +// Copyright 2014 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. + +package org.chromium.mojo; + +import android.test.InstrumentationTestCase; + +import org.chromium.base.ContextUtils; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.library_loader.LibraryLoader; +import org.chromium.base.library_loader.LibraryProcessType; + +/** + * Base class to test mojo. Setup the environment. + */ +@JNINamespace("mojo::android") +public class MojoTestCase extends InstrumentationTestCase { + + private long mTestEnvironmentPointer; + + /** + * @see junit.framework.TestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + ContextUtils.initApplicationContext( + getInstrumentation().getTargetContext().getApplicationContext()); + LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER).ensureInitialized(); + nativeInit(); + mTestEnvironmentPointer = nativeSetupTestEnvironment(); + } + + /** + * @see android.test.InstrumentationTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + nativeTearDownTestEnvironment(mTestEnvironmentPointer); + super.tearDown(); + } + + /** + * Runs the run loop for the given time. + */ + protected void runLoop(long timeoutMS) { + nativeRunLoop(timeoutMS); + } + + /** + * Runs the run loop until no handle or task are immediately available. + */ + protected void runLoopUntilIdle() { + nativeRunLoop(0); + } + + private native void nativeInit(); + + private native long nativeSetupTestEnvironment(); + + private native void nativeTearDownTestEnvironment(long testEnvironment); + + private native void nativeRunLoop(long timeoutMS); + +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java b/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..d10d0d75586ef0f420d7cb10da581e950375bd14 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java @@ -0,0 +1,32 @@ +// Copyright 2014 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. + +package org.chromium.mojo; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Random; + +/** + * Utilities methods for tests. + */ +public final class TestUtils { + + private static final Random RANDOM = new Random(); + + /** + * Returns a new direct ByteBuffer of the given size with random (but reproducible) data. + */ + public static ByteBuffer newRandomBuffer(int size) { + byte bytes[] = new byte[size]; + RANDOM.setSeed(size); + RANDOM.nextBytes(bytes); + ByteBuffer data = ByteBuffer.allocateDirect(size); + data.order(ByteOrder.LITTLE_ENDIAN); + data.put(bytes); + data.flip(); + return data; + } + +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..38bd3482e439709e492dfb41cf0cb7001b8d4665 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java @@ -0,0 +1,55 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import java.nio.charset.Charset; + +/** + * Testing {@link BindingsHelper}. + */ +public class BindingsHelperTest extends TestCase { + + /** + * Testing {@link BindingsHelper#utf8StringSizeInBytes(String)}. + */ + @SmallTest + public void testUTF8StringLength() { + String[] stringsToTest = { + "", + "a", + "hello world", + "éléphant", + "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕", + "你午饭想吃什么", + "你午饭想吃什么\0éléphant", + }; + for (String s : stringsToTest) { + assertEquals(s.getBytes(Charset.forName("utf8")).length, + BindingsHelper.utf8StringSizeInBytes(s)); + } + assertEquals(1, BindingsHelper.utf8StringSizeInBytes("\0")); + String s = new StringBuilder().appendCodePoint(0x0).appendCodePoint(0x80) + .appendCodePoint(0x800).appendCodePoint(0x10000).toString(); + assertEquals(10, BindingsHelper.utf8StringSizeInBytes(s)); + assertEquals(10, s.getBytes(Charset.forName("utf8")).length); + } + + /** + * Testing {@link BindingsHelper#align(int)}. + */ + @SmallTest + public void testAlign() { + for (int i = 0; i < 3 * BindingsHelper.ALIGNMENT; ++i) { + int j = BindingsHelper.align(i); + assertTrue(j >= i); + assertTrue(j % BindingsHelper.ALIGNMENT == 0); + assertTrue(j - i < BindingsHelper.ALIGNMENT); + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d280c774c68d1cbe58c447e16df1405e7547f029 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java @@ -0,0 +1,241 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.bindings.test.mojom.imported.Color; +import org.chromium.mojo.bindings.test.mojom.imported.Point; +import org.chromium.mojo.bindings.test.mojom.imported.Shape; +import org.chromium.mojo.bindings.test.mojom.imported.Thing; +import org.chromium.mojo.bindings.test.mojom.sample.Bar; +import org.chromium.mojo.bindings.test.mojom.sample.Bar.Type; +import org.chromium.mojo.bindings.test.mojom.sample.DefaultsTest; +import org.chromium.mojo.bindings.test.mojom.sample.Enum; +import org.chromium.mojo.bindings.test.mojom.sample.Foo; +import org.chromium.mojo.bindings.test.mojom.sample.InterfaceConstants; +import org.chromium.mojo.bindings.test.mojom.sample.SampleServiceConstants; +import org.chromium.mojo.bindings.test.mojom.test_structs.EmptyStruct; +import org.chromium.mojo.bindings.test.mojom.test_structs.Rect; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.MessagePipeHandle; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +/** + * Testing generated classes and associated features. + */ +public class BindingsTest extends TestCase { + + /** + * Create a new typical Bar instance. + */ + private static Bar newBar() { + Bar bar = new Bar(); + bar.alpha = (byte) 0x01; + bar.beta = (byte) 0x02; + bar.gamma = (byte) 0x03; + bar.type = Type.BOTH; + return bar; + } + + /** + * Create a new typical Foo instance. + */ + private static Foo createFoo() { + Foo foo = new Foo(); + foo.name = "HELLO WORLD"; + foo.arrayOfArrayOfBools = new boolean[][] { + { true, false, true }, { }, { }, { false }, { true } }; + foo.bar = newBar(); + foo.a = true; + foo.c = true; + foo.data = new byte[] { 0x01, 0x02, 0x03 }; + foo.extraBars = new Bar[] { newBar(), newBar() }; + String[][][] strings = new String[3][2][1]; + for (int i0 = 0; i0 < strings.length; ++i0) { + for (int i1 = 0; i1 < strings[i0].length; ++i1) { + for (int i2 = 0; i2 < strings[i0][i1].length; ++i2) { + strings[i0][i1][i2] = "Hello(" + i0 + ", " + i1 + ", " + i2 + ")"; + } + } + } + foo.multiArrayOfStrings = strings; + ConsumerHandle[] inputStreams = new ConsumerHandle[5]; + for (int i = 0; i < inputStreams.length; ++i) { + inputStreams[i] = new HandleMock(); + } + foo.inputStreams = inputStreams; + ProducerHandle[] outputStreams = new ProducerHandle[3]; + for (int i = 0; i < outputStreams.length; ++i) { + outputStreams[i] = new HandleMock(); + } + foo.outputStreams = outputStreams; + foo.source = new HandleMock(); + return foo; + } + + private static Rect createRect(int x, int y, int width, int height) { + Rect rect = new Rect(); + rect.x = x; + rect.y = y; + rect.width = width; + rect.height = height; + return rect; + } + + private static void checkConstantField( + Field field, Class expectedClass, T value) throws IllegalAccessException { + assertEquals(expectedClass, field.getType()); + assertEquals(Modifier.FINAL, field.getModifiers() & Modifier.FINAL); + assertEquals(Modifier.STATIC, field.getModifiers() & Modifier.STATIC); + assertEquals(value, field.get(null)); + } + + private static void checkField(Field field, Class expectedClass, + Object object, T value) throws IllegalArgumentException, IllegalAccessException { + assertEquals(expectedClass, field.getType()); + assertEquals(0, field.getModifiers() & Modifier.FINAL); + assertEquals(0, field.getModifiers() & Modifier.STATIC); + assertEquals(value, field.get(object)); + } + + /** + * Testing constants are correctly generated. + */ + @SmallTest + public void testConstants() throws NoSuchFieldException, SecurityException, + IllegalAccessException { + checkConstantField(SampleServiceConstants.class.getField("TWELVE"), byte.class, (byte) 12); + checkConstantField(InterfaceConstants.class.getField("LONG"), long.class, 4405L); + } + + /** + * Testing enums are correctly generated. + */ + @SmallTest + public void testEnums() throws NoSuchFieldException, SecurityException, + IllegalAccessException { + checkConstantField(Color.class.getField("RED"), int.class, 0); + checkConstantField(Color.class.getField("BLACK"), int.class, 1); + + checkConstantField(Enum.class.getField("VALUE"), int.class, 0); + + checkConstantField(Shape.class.getField("RECTANGLE"), int.class, 1); + checkConstantField(Shape.class.getField("CIRCLE"), int.class, 2); + checkConstantField(Shape.class.getField("TRIANGLE"), int.class, 3); + } + + /** + * Testing default values on structs. + * + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + @SmallTest + public void testStructDefaults() throws NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException { + // Check default values. + DefaultsTest test = new DefaultsTest(); + + checkField(DefaultsTest.class.getField("a0"), byte.class, test, (byte) -12); + checkField(DefaultsTest.class.getField("a1"), byte.class, test, (byte) 12); + checkField(DefaultsTest.class.getField("a2"), short.class, test, (short) 1234); + checkField(DefaultsTest.class.getField("a3"), short.class, test, (short) 34567); + checkField(DefaultsTest.class.getField("a4"), int.class, test, 123456); + checkField(DefaultsTest.class.getField("a5"), int.class, test, (int) 3456789012L); + checkField(DefaultsTest.class.getField("a6"), long.class, test, -111111111111L); + // -8446744073709551617 == 9999999999999999999 - 2 ^ 64. + checkField(DefaultsTest.class.getField("a7"), long.class, test, -8446744073709551617L); + checkField(DefaultsTest.class.getField("a8"), int.class, test, 0x12345); + checkField(DefaultsTest.class.getField("a9"), int.class, test, -0x12345); + checkField(DefaultsTest.class.getField("a10"), int.class, test, 1234); + checkField(DefaultsTest.class.getField("a11"), boolean.class, test, true); + checkField(DefaultsTest.class.getField("a12"), boolean.class, test, false); + checkField(DefaultsTest.class.getField("a13"), float.class, test, (float) 123.25); + checkField(DefaultsTest.class.getField("a14"), double.class, test, 1234567890.123); + checkField(DefaultsTest.class.getField("a15"), double.class, test, 1E10); + checkField(DefaultsTest.class.getField("a16"), double.class, test, -1.2E+20); + checkField(DefaultsTest.class.getField("a17"), double.class, test, +1.23E-20); + checkField(DefaultsTest.class.getField("a18"), byte[].class, test, null); + checkField(DefaultsTest.class.getField("a19"), String.class, test, null); + checkField(DefaultsTest.class.getField("a20"), int.class, test, Bar.Type.BOTH); + checkField(DefaultsTest.class.getField("a21"), Point.class, test, null); + + assertNotNull(test.a22); + checkField(DefaultsTest.class.getField("a22"), Thing.class, test, test.a22); + checkField(DefaultsTest.class.getField("a23"), long.class, test, -1L); + checkField(DefaultsTest.class.getField("a24"), long.class, test, 0x123456789L); + checkField(DefaultsTest.class.getField("a25"), long.class, test, -0x123456789L); + } + + /** + * Testing generation of the Foo class. + * + * @throws IllegalAccessException + */ + @SmallTest + public void testFooGeneration() throws NoSuchFieldException, SecurityException, + IllegalAccessException { + // Checking Foo constants. + checkConstantField(Foo.class.getField("FOOBY"), String.class, "Fooby"); + + // Checking Foo default values. + Foo foo = new Foo(); + checkField(Foo.class.getField("name"), String.class, foo, Foo.FOOBY); + + assertNotNull(foo.source); + assertFalse(foo.source.isValid()); + checkField(Foo.class.getField("source"), MessagePipeHandle.class, foo, foo.source); + } + + /** + * Testing serialization of the Foo class. + */ + @SmallTest + public void testFooSerialization() { + // Checking serialization and deserialization of a Foo object. + Foo typicalFoo = createFoo(); + Message serializedFoo = typicalFoo.serialize(null); + Foo deserializedFoo = Foo.deserialize(serializedFoo); + assertEquals(typicalFoo, deserializedFoo); + } + + /** + * Testing serialization of the EmptyStruct class. + */ + @SmallTest + public void testEmptyStructSerialization() { + // Checking serialization and deserialization of a EmptyStruct object. + Message serializedStruct = new EmptyStruct().serialize(null); + EmptyStruct emptyStruct = EmptyStruct.deserialize(serializedStruct); + assertNotNull(emptyStruct); + } + + // In testing maps we want to make sure that the key used when inserting an + // item the key used when looking it up again are different objects. Java + // has default implementations of equals and hashCode that use reference + // equality and hashing, respectively, and that's not what we want for our + // mojom values. + @SmallTest + public void testHashMapStructKey() { + Map map = new HashMap<>(); + map.put(createRect(1, 2, 3, 4), 123); + + Rect key = createRect(1, 2, 3, 4); + assertNotNull(map.get(key)); + assertEquals(123, map.get(key).intValue()); + + map.remove(key); + assertTrue(map.isEmpty()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..5554f805a7e42e82dceb8747672f7ba1cead5306 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java @@ -0,0 +1,108 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.TestUtils; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.Closeable; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class for bindings tests. + */ +public class BindingsTestUtils { + + /** + * {@link MessageReceiver} that records any message it receives. + */ + public static class RecordingMessageReceiver extends SideEffectFreeCloseable + implements MessageReceiver { + + public final List messages = new ArrayList(); + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + messages.add(message); + return true; + } + } + + /** + * {@link MessageReceiverWithResponder} that records any message it receives. + */ + public static class RecordingMessageReceiverWithResponder extends RecordingMessageReceiver + implements MessageReceiverWithResponder { + + public final List> messagesWithReceivers = + new ArrayList>(); + + /** + * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver) + */ + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + messagesWithReceivers.add(Pair.create(message, responder)); + return true; + } + } + + /** + * {@link ConnectionErrorHandler} that records any error it received. + */ + public static class CapturingErrorHandler implements ConnectionErrorHandler { + + private MojoException mLastMojoException = null; + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + mLastMojoException = e; + } + + /** + * Returns the last recorded exception. + */ + public MojoException getLastMojoException() { + return mLastMojoException; + } + + } + + /** + * Creates a new valid {@link Message}. The message will have a valid header. + */ + public static Message newRandomMessage(int size) { + assert size > 16; + ByteBuffer message = TestUtils.newRandomBuffer(size); + int[] headerAsInts = {16, 2, 0, 0}; + for (int i = 0; i < 4; ++i) { + message.putInt(4 * i, headerAsInts[i]); + } + message.position(0); + return new Message(message, new ArrayList()); + } + + public static P newProxyOverPipe( + Interface.Manager manager, I impl, List toClose) { + Pair handles = + CoreImpl.getInstance().createMessagePipe(null); + P proxy = manager.attachProxy(handles.first, 0); + toClose.add(proxy); + manager.bind(impl, handles.second); + return proxy; + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java new file mode 100644 index 0000000000000000000000000000000000000000..eea92ab7b881bd6545517bdf9577c0f622231511 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java @@ -0,0 +1,211 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStruct; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV0; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV1; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV3; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV5; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV7; +import org.chromium.mojo.bindings.test.mojom.test_structs.Rect; +import org.chromium.mojo.system.impl.CoreImpl; + +/** + * Testing generated classes with the [MinVersion] annotation. Struct in this test are from: + * mojo/public/interfaces/bindings/tests/rect.mojom and + * mojo/public/interfaces/bindings/tests/test_structs.mojom + */ +public class BindingsVersioningTest extends MojoTestCase { + private static Rect newRect(int factor) { + Rect rect = new Rect(); + rect.x = factor; + rect.y = 2 * factor; + rect.width = 10 * factor; + rect.height = 20 * factor; + return rect; + } + + private static MultiVersionStruct newStruct() { + MultiVersionStruct struct = new MultiVersionStruct(); + struct.fInt32 = 123; + struct.fRect = newRect(5); + struct.fString = "hello"; + struct.fArray = new byte[] {10, 9, 8}; + struct.fBool = true; + struct.fInt16 = 256; + return struct; + } + + /** + * Testing serializing old struct version to newer one. + */ + @SmallTest + public void testOldToNew() { + { + MultiVersionStructV0 v0 = new MultiVersionStructV0(); + v0.fInt32 = 123; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v0.serialize(null)); + assertEquals(expected, output); + assertEquals(0, v0.getVersion()); + assertEquals(0, output.getVersion()); + } + + { + MultiVersionStructV1 v1 = new MultiVersionStructV1(); + v1.fInt32 = 123; + v1.fRect = newRect(5); + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + + MultiVersionStruct output = MultiVersionStruct.deserialize(v1.serialize(null)); + assertEquals(expected, output); + assertEquals(1, v1.getVersion()); + assertEquals(1, output.getVersion()); + } + + { + MultiVersionStructV3 v3 = new MultiVersionStructV3(); + v3.fInt32 = 123; + v3.fRect = newRect(5); + v3.fString = "hello"; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v3.serialize(null)); + assertEquals(expected, output); + assertEquals(3, v3.getVersion()); + assertEquals(3, output.getVersion()); + } + + { + MultiVersionStructV5 v5 = new MultiVersionStructV5(); + v5.fInt32 = 123; + v5.fRect = newRect(5); + v5.fString = "hello"; + v5.fArray = new byte[] {10, 9, 8}; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v5.serialize(null)); + assertEquals(expected, output); + assertEquals(5, v5.getVersion()); + assertEquals(5, output.getVersion()); + } + + { + int expectedHandle = 42; + MultiVersionStructV7 v7 = new MultiVersionStructV7(); + v7.fInt32 = 123; + v7.fRect = newRect(5); + v7.fString = "hello"; + v7.fArray = new byte[] {10, 9, 8}; + v7.fMessagePipe = CoreImpl.getInstance() + .acquireNativeHandle(expectedHandle) + .toMessagePipeHandle(); + v7.fBool = true; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + expected.fBool = true; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v7.serialize(null)); + + // Handles must be tested separately. + assertEquals(expectedHandle, output.fMessagePipe.releaseNativeHandle()); + output.fMessagePipe = expected.fMessagePipe; + + assertEquals(expected, output); + assertEquals(7, v7.getVersion()); + assertEquals(7, output.getVersion()); + } + } + + /** + * Testing serializing new struct version to older one. + */ + @SmallTest + public void testNewToOld() { + MultiVersionStruct struct = newStruct(); + { + MultiVersionStructV0 expected = new MultiVersionStructV0(); + expected.fInt32 = 123; + + MultiVersionStructV0 output = MultiVersionStructV0.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV1 expected = new MultiVersionStructV1(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + + MultiVersionStructV1 output = MultiVersionStructV1.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV3 expected = new MultiVersionStructV3(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + + MultiVersionStructV3 output = MultiVersionStructV3.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV5 expected = new MultiVersionStructV5(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + + MultiVersionStructV5 output = MultiVersionStructV5.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + int expectedHandle = 42; + MultiVersionStructV7 expected = new MultiVersionStructV7(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + expected.fBool = true; + + MultiVersionStruct input = struct; + input.fMessagePipe = CoreImpl.getInstance() + .acquireNativeHandle(expectedHandle) + .toMessagePipeHandle(); + + MultiVersionStructV7 output = MultiVersionStructV7.deserialize(input.serialize(null)); + + assertEquals(expectedHandle, output.fMessagePipe.releaseNativeHandle()); + output.fMessagePipe = expected.fMessagePipe; + + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java new file mode 100644 index 0000000000000000000000000000000000000000..497be65af218992574980cff4dc47b8f83b3202a --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java @@ -0,0 +1,59 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.bindings.Callbacks.Callback1; +import org.chromium.mojo.bindings.Callbacks.Callback7; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Testing generated callbacks + */ +public class CallbacksTest extends TestCase { + + /** + * Testing {@link Callback1}. + */ + @SmallTest + public void testCallback1() { + final List parameters = new ArrayList(); + new Callback1() { + @Override + public void call(Integer i1) { + parameters.add(i1); + } + }.call(1); + assertEquals(Arrays.asList(1), parameters); + } + + /** + * Testing {@link Callback7}. + */ + @SmallTest + public void testCallback7() { + final List parameters = new ArrayList(); + new Callback7() { + @Override + public void call(Integer i1, Integer i2, Integer i3, Integer i4, Integer i5, Integer i6, + Integer i7) { + parameters.add(i1); + parameters.add(i2); + parameters.add(i3); + parameters.add(i4); + parameters.add(i5); + parameters.add(i6); + parameters.add(i7); + } + }.call(1, 2, 3, 4, 5, 6, 7); + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7), parameters); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..15f9f1fa33829b32d023bbd5ca4ab7afbe3c8943 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java @@ -0,0 +1,108 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiver; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * Testing the {@link Connector} class. + */ +public class ConnectorTest extends MojoTestCase { + + private static final int DATA_LENGTH = 1024; + + private MessagePipeHandle mHandle; + private Connector mConnector; + private Message mTestMessage; + private RecordingMessageReceiver mReceiver; + private CapturingErrorHandler mErrorHandler; + + /** + * @see MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe( + new MessagePipeHandle.CreateOptions()); + mHandle = handles.first; + mConnector = new Connector(handles.second); + mReceiver = new RecordingMessageReceiver(); + mConnector.setIncomingMessageReceiver(mReceiver); + mErrorHandler = new CapturingErrorHandler(); + mConnector.setErrorHandler(mErrorHandler); + mConnector.start(); + mTestMessage = BindingsTestUtils.newRandomMessage(DATA_LENGTH); + assertNull(mErrorHandler.getLastMojoException()); + assertEquals(0, mReceiver.messages.size()); + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + mConnector.close(); + mHandle.close(); + super.tearDown(); + } + + /** + * Test sending a message through a {@link Connector}. + */ + @SmallTest + public void testSendingMessage() { + mConnector.accept(mTestMessage); + assertNull(mErrorHandler.getLastMojoException()); + ByteBuffer received = ByteBuffer.allocateDirect(DATA_LENGTH); + ResultAnd result = + mHandle.readMessage(received, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(DATA_LENGTH, result.getValue().getMessageSize()); + assertEquals(mTestMessage.getData(), received); + } + + /** + * Test receiving a message through a {@link Connector} + */ + @SmallTest + public void testReceivingMessage() { + mHandle.writeMessage(mTestMessage.getData(), new ArrayList(), + MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + assertNull(mErrorHandler.getLastMojoException()); + assertEquals(1, mReceiver.messages.size()); + Message received = mReceiver.messages.get(0); + assertEquals(0, received.getHandles().size()); + assertEquals(mTestMessage.getData(), received.getData()); + } + + /** + * Test receiving an error through a {@link Connector}. + */ + @SmallTest + public void testErrors() { + mHandle.close(); + runLoopUntilIdle(); + assertNotNull(mErrorHandler.getLastMojoException()); + assertEquals(MojoResult.FAILED_PRECONDITION, + mErrorHandler.getLastMojoException().getMojoResult()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cabe2306b88b7cfc980ba1feaf241c30f642348d --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java @@ -0,0 +1,104 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Testing the executor factory. + */ +public class ExecutorFactoryTest extends MojoTestCase { + + private static final long RUN_LOOP_TIMEOUT_MS = 50; + private static final int CONCURRENCY_LEVEL = 5; + private static final ExecutorService WORKERS = Executors.newFixedThreadPool(CONCURRENCY_LEVEL); + + private Executor mExecutor; + private List mThreadContainer; + + /** + * @see MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + mExecutor = ExecutorFactory.getExecutorForCurrentThread(CoreImpl.getInstance()); + mThreadContainer = new ArrayList(); + } + + /** + * Testing the {@link Executor} when called from the executor thread. + */ + @SmallTest + public void testExecutorOnCurrentThread() { + Runnable action = new Runnable() { + @Override + public void run() { + mThreadContainer.add(Thread.currentThread()); + } + }; + mExecutor.execute(action); + mExecutor.execute(action); + assertEquals(0, mThreadContainer.size()); + runLoop(RUN_LOOP_TIMEOUT_MS); + assertEquals(2, mThreadContainer.size()); + for (Thread thread : mThreadContainer) { + assertEquals(Thread.currentThread(), thread); + } + } + + /** + * Testing the {@link Executor} when called from another thread. + */ + @SmallTest + public void testExecutorOnOtherThread() { + final CyclicBarrier barrier = new CyclicBarrier(CONCURRENCY_LEVEL + 1); + for (int i = 0; i < CONCURRENCY_LEVEL; ++i) { + WORKERS.execute(new Runnable() { + @Override + public void run() { + mExecutor.execute(new Runnable() { + + @Override + public void run() { + mThreadContainer.add(Thread.currentThread()); + } + }); + try { + barrier.await(); + } catch (InterruptedException e) { + fail("Unexpected exception: " + e.getMessage()); + } catch (BrokenBarrierException e) { + fail("Unexpected exception: " + e.getMessage()); + } + } + }); + } + try { + barrier.await(); + } catch (InterruptedException e) { + fail("Unexpected exception: " + e.getMessage()); + } catch (BrokenBarrierException e) { + fail("Unexpected exception: " + e.getMessage()); + } + assertEquals(0, mThreadContainer.size()); + runLoop(RUN_LOOP_TIMEOUT_MS); + assertEquals(CONCURRENCY_LEVEL, mThreadContainer.size()); + for (Thread thread : mThreadContainer) { + assertEquals(Thread.currentThread(), thread); + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8cdd4abf29e810830a2abb49440a8f97ab1aa57b --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java @@ -0,0 +1,129 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.Callbacks.Callback1; +import org.chromium.mojo.bindings.test.mojom.sample.Enum; +import org.chromium.mojo.bindings.test.mojom.sample.IntegerAccessor; +import org.chromium.mojo.system.MojoException; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Tests for interface control messages. + */ +public class InterfaceControlMessageTest extends MojoTestCase { + private final List mCloseablesToClose = new ArrayList(); + + /** + * See mojo/public/interfaces/bindings/tests/sample_interfaces.mojom. + */ + class IntegerAccessorImpl extends SideEffectFreeCloseable implements IntegerAccessor { + private long mValue = 0; + private int mEnum = 0; + private boolean mEncounteredError = false; + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + mEncounteredError = true; + } + + /** + * @see IntegerAccessor#getInteger(IntegerAccessor.GetIntegerResponse) + */ + @Override + public void getInteger(GetIntegerResponse response) { + response.call(mValue, mEnum); + } + + /** + * @see IntegerAccessor#setInteger(long, int) + */ + @Override + public void setInteger(long value, int enumValue) { + mValue = value; + mEnum = enumValue; + } + + public long getValue() { + return mValue; + } + + public boolean encounteredError() { + return mEncounteredError; + } + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + // Close the elements in the reverse order they were added. This is needed because it is an + // error to close the handle of a proxy without closing the proxy first. + Collections.reverse(mCloseablesToClose); + for (Closeable c : mCloseablesToClose) { + c.close(); + } + super.tearDown(); + } + + @SmallTest + public void testQueryVersion() { + IntegerAccessor.Proxy p = BindingsTestUtils.newProxyOverPipe( + IntegerAccessor.MANAGER, new IntegerAccessorImpl(), mCloseablesToClose); + assertEquals(0, p.getProxyHandler().getVersion()); + p.getProxyHandler().queryVersion(new Callback1() { + @Override + public void call(Integer version) { + assertEquals(3, version.intValue()); + } + }); + runLoopUntilIdle(); + assertEquals(3, p.getProxyHandler().getVersion()); + } + + @SmallTest + public void testRequireVersion() { + IntegerAccessorImpl impl = new IntegerAccessorImpl(); + IntegerAccessor.Proxy p = BindingsTestUtils.newProxyOverPipe( + IntegerAccessor.MANAGER, impl, mCloseablesToClose); + + assertEquals(0, p.getProxyHandler().getVersion()); + + p.getProxyHandler().requireVersion(1); + assertEquals(1, p.getProxyHandler().getVersion()); + p.setInteger(123, Enum.VALUE); + runLoopUntilIdle(); + assertFalse(impl.encounteredError()); + assertEquals(123, impl.getValue()); + + p.getProxyHandler().requireVersion(3); + assertEquals(3, p.getProxyHandler().getVersion()); + p.setInteger(456, Enum.VALUE); + runLoopUntilIdle(); + assertFalse(impl.encounteredError()); + assertEquals(456, impl.getValue()); + + // Require a version that is not supported by the implementation side. + p.getProxyHandler().requireVersion(4); + // getVersion() is updated synchronously. + assertEquals(4, p.getProxyHandler().getVersion()); + p.setInteger(789, Enum.VALUE); + runLoopUntilIdle(); + assertTrue(impl.encounteredError()); + // The call to setInteger() after requireVersion() is ignored. + assertEquals(456, impl.getValue()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d8bd3e85adb86b2479563e2c7c401cb870e20273 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java @@ -0,0 +1,284 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.test.mojom.imported.ImportedInterface; +import org.chromium.mojo.bindings.test.mojom.sample.Factory; +import org.chromium.mojo.bindings.test.mojom.sample.NamedObject; +import org.chromium.mojo.bindings.test.mojom.sample.NamedObject.GetNameResponse; +import org.chromium.mojo.bindings.test.mojom.sample.Request; +import org.chromium.mojo.bindings.test.mojom.sample.Response; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Tests for interfaces / proxies / stubs generated for sample_factory.mojom. + */ +public class InterfacesTest extends MojoTestCase { + + private static final String OBJECT_NAME = "hello world"; + + private final List mCloseablesToClose = new ArrayList(); + + /** + * Basic implementation of {@link NamedObject}. + */ + public static class MockNamedObjectImpl extends CapturingErrorHandler implements NamedObject { + + private String mName = ""; + + /** + * @see org.chromium.mojo.bindings.Interface#close() + */ + @Override + public void close() { + } + + @Override + public void setName(String name) { + mName = name; + } + + @Override + public void getName(GetNameResponse callback) { + callback.call(mName); + } + + public String getNameSynchronously() { + return mName; + } + } + + /** + * Implementation of {@link GetNameResponse} keeping track of usage. + */ + public static class RecordingGetNameResponse implements GetNameResponse { + private String mName; + private boolean mCalled; + + public RecordingGetNameResponse() { + reset(); + } + + @Override + public void call(String name) { + mName = name; + mCalled = true; + } + + public String getName() { + return mName; + } + + public boolean wasCalled() { + return mCalled; + } + + public void reset() { + mName = null; + mCalled = false; + } + } + + /** + * Basic implementation of {@link Factory}. + */ + public class MockFactoryImpl extends CapturingErrorHandler implements Factory { + + private boolean mClosed = false; + + public boolean isClosed() { + return mClosed; + } + + /** + * @see org.chromium.mojo.bindings.Interface#close() + */ + @Override + public void close() { + mClosed = true; + } + + @Override + public void doStuff(Request request, MessagePipeHandle pipe, DoStuffResponse callback) { + if (pipe != null) { + pipe.close(); + } + Response response = new Response(); + response.x = 42; + callback.call(response, "Hello"); + } + + @Override + public void doStuff2(ConsumerHandle pipe, DoStuff2Response callback) { + callback.call("World"); + } + + @Override + public void createNamedObject(InterfaceRequest obj) { + NamedObject.MANAGER.bind(new MockNamedObjectImpl(), obj); + } + + @Override + public void requestImportedInterface(InterfaceRequest obj, + RequestImportedInterfaceResponse callback) { + throw new UnsupportedOperationException("Not implemented."); + } + + @Override + public void takeImportedInterface(ImportedInterface obj, + TakeImportedInterfaceResponse callback) { + throw new UnsupportedOperationException("Not implemented."); + } + } + + /** + * Implementation of DoStuffResponse that keeps track of if the response is called. + */ + public static class DoStuffResponseImpl implements Factory.DoStuffResponse { + private boolean mResponseCalled = false; + + public boolean wasResponseCalled() { + return mResponseCalled; + } + + @Override + public void call(Response response, String string) { + mResponseCalled = true; + } + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + // Close the elements in the reverse order they were added. This is needed because it is an + // error to close the handle of a proxy without closing the proxy first. + Collections.reverse(mCloseablesToClose); + for (Closeable c : mCloseablesToClose) { + c.close(); + } + super.tearDown(); + } + + /** + * Check that the given proxy receives the calls. If |impl| is not null, also check that the + * calls are forwared to |impl|. + */ + private void checkProxy(NamedObject.Proxy proxy, MockNamedObjectImpl impl) { + RecordingGetNameResponse callback = new RecordingGetNameResponse(); + CapturingErrorHandler errorHandler = new CapturingErrorHandler(); + proxy.getProxyHandler().setErrorHandler(errorHandler); + + if (impl != null) { + assertNull(impl.getLastMojoException()); + assertEquals("", impl.getNameSynchronously()); + } + + proxy.getName(callback); + runLoopUntilIdle(); + + assertNull(errorHandler.getLastMojoException()); + assertTrue(callback.wasCalled()); + assertEquals("", callback.getName()); + + callback.reset(); + proxy.setName(OBJECT_NAME); + runLoopUntilIdle(); + + assertNull(errorHandler.getLastMojoException()); + if (impl != null) { + assertNull(impl.getLastMojoException()); + assertEquals(OBJECT_NAME, impl.getNameSynchronously()); + } + + proxy.getName(callback); + runLoopUntilIdle(); + + assertNull(errorHandler.getLastMojoException()); + assertTrue(callback.wasCalled()); + assertEquals(OBJECT_NAME, callback.getName()); + } + + @SmallTest + public void testName() { + assertEquals("sample::NamedObject", NamedObject.MANAGER.getName()); + } + + @SmallTest + public void testProxyAndStub() { + MockNamedObjectImpl impl = new MockNamedObjectImpl(); + NamedObject.Proxy proxy = + NamedObject.MANAGER.buildProxy(null, NamedObject.MANAGER.buildStub(null, impl)); + + checkProxy(proxy, impl); + } + + @SmallTest + public void testProxyAndStubOverPipe() { + MockNamedObjectImpl impl = new MockNamedObjectImpl(); + NamedObject.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(NamedObject.MANAGER, impl, mCloseablesToClose); + + checkProxy(proxy, impl); + } + + @SmallTest + public void testFactoryOverPipe() { + Factory.Proxy proxy = BindingsTestUtils.newProxyOverPipe( + Factory.MANAGER, new MockFactoryImpl(), mCloseablesToClose); + Pair> request = + NamedObject.MANAGER.getInterfaceRequest(CoreImpl.getInstance()); + mCloseablesToClose.add(request.first); + proxy.createNamedObject(request.second); + + checkProxy(request.first, null); + } + + @SmallTest + public void testInterfaceClosing() { + MockFactoryImpl impl = new MockFactoryImpl(); + Factory.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(Factory.MANAGER, impl, mCloseablesToClose); + + assertFalse(impl.isClosed()); + + proxy.close(); + runLoopUntilIdle(); + + assertTrue(impl.isClosed()); + } + + @SmallTest + public void testResponse() { + MockFactoryImpl impl = new MockFactoryImpl(); + Factory.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(Factory.MANAGER, impl, mCloseablesToClose); + Request request = new Request(); + request.x = 42; + Pair handles = + CoreImpl.getInstance().createMessagePipe(null); + DoStuffResponseImpl response = new DoStuffResponseImpl(); + proxy.doStuff(request, handles.first, response); + + assertFalse(response.wasResponseCalled()); + + runLoopUntilIdle(); + + assertTrue(response.wasResponseCalled()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b2e6ac85213b48e3928ada0e4bf9ef2352476a91 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java @@ -0,0 +1,69 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.bindings.test.mojom.imported.Point; + +/** + * Testing internal classes of interfaces. + */ +public class MessageHeaderTest extends TestCase { + + /** + * Testing that headers are identical after being serialized/deserialized. + */ + @SmallTest + public void testSimpleMessageHeader() { + final int xValue = 1; + final int yValue = 2; + final int type = 6; + Point p = new Point(); + p.x = xValue; + p.y = yValue; + ServiceMessage message = p.serializeWithHeader(null, new MessageHeader(type)); + + MessageHeader header = message.getHeader(); + assertTrue(header.validateHeader(type, 0)); + assertEquals(type, header.getType()); + assertEquals(0, header.getFlags()); + + Point p2 = Point.deserialize(message.getPayload()); + assertNotNull(p2); + assertEquals(p.x, p2.x); + assertEquals(p.y, p2.y); + } + + /** + * Testing that headers are identical after being serialized/deserialized. + */ + @SmallTest + public void testMessageWithRequestIdHeader() { + final int xValue = 1; + final int yValue = 2; + final int type = 6; + final long requestId = 0x1deadbeafL; + Point p = new Point(); + p.x = xValue; + p.y = yValue; + ServiceMessage message = p.serializeWithHeader(null, + new MessageHeader(type, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0)); + message.setRequestId(requestId); + + MessageHeader header = message.getHeader(); + assertTrue(header.validateHeader(type, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)); + assertEquals(type, header.getType()); + assertEquals(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, header.getFlags()); + assertEquals(requestId, header.getRequestId()); + + Point p2 = Point.deserialize(message.getPayload()); + assertNotNull(p2); + assertEquals(p.x, p2.x); + assertEquals(p.y, p2.y); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..51dbd22800b3c35a86f429bf37cecced2de2dc0a --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java @@ -0,0 +1,109 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiver; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Testing {@link Connector#readAndDispatchMessage}. + */ +public class ReadAndDispatchMessageTest extends MojoTestCase { + + private static final int DATA_SIZE = 1024; + + private ByteBuffer mData; + private Pair mHandles; + private List mHandlesToSend = new ArrayList(); + private List mHandlesToClose = new ArrayList(); + private RecordingMessageReceiver mMessageReceiver; + + /** + * @see org.chromium.mojo.MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + Core core = CoreImpl.getInstance(); + mData = BindingsTestUtils.newRandomMessage(DATA_SIZE).getData(); + mMessageReceiver = new RecordingMessageReceiver(); + mHandles = core.createMessagePipe(new MessagePipeHandle.CreateOptions()); + Pair datapipe = core.createDataPipe(null); + mHandlesToSend.addAll(Arrays.asList(datapipe.first, datapipe.second)); + mHandlesToClose.addAll(Arrays.asList(mHandles.first, mHandles.second)); + mHandlesToClose.addAll(mHandlesToSend); + } + + /** + * @see org.chromium.mojo.MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + for (Handle handle : mHandlesToClose) { + handle.close(); + } + super.tearDown(); + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + */ + @SmallTest + public void testReadAndDispatchMessage() { + mHandles.first.writeMessage(mData, mHandlesToSend, MessagePipeHandle.WriteFlags.NONE); + assertEquals(MojoResult.OK, Connector.readAndDispatchMessage(mHandles.second, + mMessageReceiver).getMojoResult()); + assertEquals(1, mMessageReceiver.messages.size()); + Message message = mMessageReceiver.messages.get(0); + mHandlesToClose.addAll(message.getHandles()); + assertEquals(mData, message.getData()); + assertEquals(2, message.getHandles().size()); + for (Handle handle : message.getHandles()) { + assertTrue(handle.isValid()); + } + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + * with no message available. + */ + @SmallTest + public void testReadAndDispatchMessageOnEmptyHandle() { + assertEquals(MojoResult.SHOULD_WAIT, Connector.readAndDispatchMessage(mHandles.second, + mMessageReceiver).getMojoResult()); + assertEquals(0, mMessageReceiver.messages.size()); + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + * on closed handle. + */ + @SmallTest + public void testReadAndDispatchMessageOnClosedHandle() { + mHandles.first.close(); + try { + Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver); + fail("MojoException should have been thrown"); + } catch (MojoException expected) { + assertEquals(MojoResult.FAILED_PRECONDITION, expected.getMojoResult()); + } + assertEquals(0, mMessageReceiver.messages.size()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6aa1726b4c79c0f4ed38270a683dc30c1d78993d --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java @@ -0,0 +1,231 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.base.annotations.SuppressFBWarnings; +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiverWithResponder; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignals; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * Testing {@link Router} + */ +public class RouterTest extends MojoTestCase { + + private MessagePipeHandle mHandle; + private Router mRouter; + private RecordingMessageReceiverWithResponder mReceiver; + private CapturingErrorHandler mErrorHandler; + + /** + * @see MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + mHandle = handles.first; + mRouter = new RouterImpl(handles.second); + mReceiver = new RecordingMessageReceiverWithResponder(); + mRouter.setIncomingMessageReceiver(mReceiver); + mErrorHandler = new CapturingErrorHandler(); + mRouter.setErrorHandler(mErrorHandler); + mRouter.start(); + } + + /** + * Testing sending a message via the router that expected a response. + */ + @SmallTest + public void testSendingToRouterWithResponse() { + final int requestMessageType = 0xdead; + final int responseMessageType = 0xbeaf; + + // Sending a message expecting a response. + MessageHeader header = new MessageHeader(requestMessageType, + MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0); + Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + header.encode(encoder); + mRouter.acceptWithResponder(encoder.getMessage(), mReceiver); + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(header.getSize()); + ResultAnd result = + mHandle.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + + assertEquals(MojoResult.OK, result.getMojoResult()); + MessageHeader receivedHeader = new Message( + receiveBuffer, new ArrayList()).asServiceMessage().getHeader(); + + assertEquals(header.getType(), receivedHeader.getType()); + assertEquals(header.getFlags(), receivedHeader.getFlags()); + assertTrue(receivedHeader.getRequestId() != 0); + + // Sending the response. + MessageHeader responseHeader = new MessageHeader(responseMessageType, + MessageHeader.MESSAGE_IS_RESPONSE_FLAG, receivedHeader.getRequestId()); + encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + responseHeader.encode(encoder); + Message responseMessage = encoder.getMessage(); + mHandle.writeMessage(responseMessage.getData(), new ArrayList(), + MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + + assertEquals(1, mReceiver.messages.size()); + ServiceMessage receivedResponseMessage = mReceiver.messages.get(0).asServiceMessage(); + assertEquals(MessageHeader.MESSAGE_IS_RESPONSE_FLAG, + receivedResponseMessage.getHeader().getFlags()); + assertEquals(responseMessage.getData(), receivedResponseMessage.getData()); + } + + /** + * Sends a message to the Router. + * + * @param messageIndex Used when sending multiple messages to indicate the index of this + * message. + * @param requestMessageType The message type to use in the header of the sent message. + * @param requestId The requestId to use in the header of the sent message. + */ + private void sendMessageToRouter(int messageIndex, int requestMessageType, int requestId) { + MessageHeader header = new MessageHeader( + requestMessageType, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, requestId); + Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + header.encode(encoder); + Message headerMessage = encoder.getMessage(); + mHandle.writeMessage(headerMessage.getData(), new ArrayList(), + MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + + assertEquals(messageIndex + 1, mReceiver.messagesWithReceivers.size()); + Pair receivedMessage = + mReceiver.messagesWithReceivers.get(messageIndex); + assertEquals(headerMessage.getData(), receivedMessage.first.getData()); + } + + /** + * Sends a response message from the Router. + * + * @param messageIndex Used when sending responses to multiple messages to indicate the index + * of the message that this message is a response to. + * @param responseMessageType The message type to use in the header of the response message. + */ + private void sendResponseFromRouter(int messageIndex, int responseMessageType) { + Pair receivedMessage = + mReceiver.messagesWithReceivers.get(messageIndex); + + long requestId = receivedMessage.first.asServiceMessage().getHeader().getRequestId(); + + MessageHeader responseHeader = new MessageHeader( + responseMessageType, MessageHeader.MESSAGE_IS_RESPONSE_FLAG, requestId); + Encoder encoder = new Encoder(CoreImpl.getInstance(), responseHeader.getSize()); + responseHeader.encode(encoder); + Message message = encoder.getMessage(); + receivedMessage.second.accept(message); + + ByteBuffer receivedResponseMessage = ByteBuffer.allocateDirect(responseHeader.getSize()); + ResultAnd result = + mHandle.readMessage(receivedResponseMessage, 0, MessagePipeHandle.ReadFlags.NONE); + + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(message.getData(), receivedResponseMessage); + } + + /** + * Clears {@code mReceiver.messagesWithReceivers} allowing all message receivers to be + * finalized. + *

+ * Since there is no way to force the Garbage Collector to actually call finalize and we want to + * test the effects of the finalize() method, we explicitly call finalize() on all of the + * message receivers. We do this in a custom thread to better approximate what the JVM does. + */ + private void clearAllMessageReceivers() { + Thread myFinalizerThread = new Thread() { + @Override + @SuppressFBWarnings("FI_EXPLICIT_INVOCATION") + public void run() { + for (Pair receivedMessage : + mReceiver.messagesWithReceivers) { + RouterImpl.ResponderThunk thunk = + (RouterImpl.ResponderThunk) receivedMessage.second; + try { + thunk.finalize(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + } + }; + myFinalizerThread.start(); + try { + myFinalizerThread.join(); + } catch (InterruptedException e) { + // ignore. + } + mReceiver.messagesWithReceivers.clear(); + } + + /** + * Testing receiving a message via the router that expected a response. + */ + @SmallTest + public void testReceivingViaRouterWithResponse() { + final int requestMessageType = 0xdead; + final int responseMessageType = 0xbeef; + final int requestId = 0xdeadbeaf; + + // Send a message expecting a response. + sendMessageToRouter(0, requestMessageType, requestId); + + // Sending the response. + sendResponseFromRouter(0, responseMessageType); + } + + /** + * Tests that if a callback is dropped (i.e. becomes unreachable and is finalized + * without being used), then the message pipe will be closed. + */ + @SmallTest + public void testDroppingReceiverWithoutUsingIt() { + // Send 10 messages to the router without sending a response. + for (int i = 0; i < 10; i++) { + sendMessageToRouter(i, i, i); + } + + // Now send the 10 responses. This should work fine. + for (int i = 0; i < 10; i++) { + sendResponseFromRouter(i, i); + } + + // Clear all MessageRecievers so that the ResponderThunks will + // be finalized. + clearAllMessageReceivers(); + + // Send another message to the router without sending a response. + sendMessageToRouter(0, 0, 0); + + // Clear the MessageReciever so that the ResponderThunk will + // be finalized. Since the RespondeThunk was never used, this + // should close the pipe. + clearAllMessageReceivers(); + // The close() occurs asynchronously on this thread. + runLoopUntilIdle(); + + // Confirm that the pipe was closed on the Router side. + HandleSignals closedFlag = HandleSignals.none().setPeerClosed(true); + assertEquals(closedFlag, mHandle.querySignalsState().getSatisfiedSignals()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2c17e3a19464f89da94a23af677131deed5a8a84 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java @@ -0,0 +1,175 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct1; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct2; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct3; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct4; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct5; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct6; +import org.chromium.mojo.bindings.test.mojom.mojo.StructOfNullables; + +import java.nio.ByteBuffer; + +/** + * Tests for the serialization logic of the generated structs, using structs defined in + * mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom . + */ +public class SerializationTest extends TestCase { + + private static void assertThrowsSerializationException(Struct struct) { + try { + struct.serialize(null); + fail("Serialization of invalid struct should have thrown an exception."); + } catch (SerializationException ex) { + // Expected. + } + } + + /** + * Verifies that serializing a struct with an invalid handle of a non-nullable type throws an + * exception. + */ + @SmallTest + public void testHandle() { + Struct2 struct = new Struct2(); + assertFalse(struct.hdl.isValid()); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.hdl = new HandleMock(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a null struct pointer throws an exception. + */ + @SmallTest + public void testStructPointer() { + Struct3 struct = new Struct3(); + assertNull(struct.struct1); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.struct1 = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with an array of structs throws an exception when the + * struct is invalid. + */ + @SmallTest + public void testStructArray() { + Struct4 struct = new Struct4(); + assertNull(struct.data); + assertThrowsSerializationException(struct); + + // Create the (1-element) array but have the element null. + struct.data = new Struct1[1]; + assertThrowsSerializationException(struct); + + // Create the array element, struct should serialize now. + struct.data[0] = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a fixed-size array of incorrect length throws an + * exception. + */ + @SmallTest + public void testFixedSizeArray() { + Struct5 struct = new Struct5(); + assertNull(struct.pair); + assertThrowsSerializationException(struct); + + // Create the (1-element) array, 2-element array is required. + struct.pair = new Struct1[1]; + struct.pair[0] = new Struct1(); + assertThrowsSerializationException(struct); + + // Create the array of a correct size, struct should serialize now. + struct.pair = new Struct1[2]; + struct.pair[0] = new Struct1(); + struct.pair[1] = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a null string throws an exception. + */ + @SmallTest + public void testString() { + Struct6 struct = new Struct6(); + assertNull(struct.str); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.str = ""; + struct.serialize(null); + } + + /** + * Verifies that a struct with an invalid nullable handle, null nullable struct pointer and null + * nullable string serializes without an exception. + */ + @SmallTest + public void testNullableFields() { + StructOfNullables struct = new StructOfNullables(); + assertFalse(struct.hdl.isValid()); + assertNull(struct.struct1); + assertNull(struct.str); + struct.serialize(null); + } + + /** + * Verifies that a struct can be serialized to and deserialized from a ByteBuffer. + */ + @SmallTest + public void testByteBufferSerialization() { + Struct1 input = new Struct1(); + input.i = 0x7F; + + ByteBuffer buf = input.serialize(); + + byte[] expected_raw_bytes = {16, 0, 0, 0, 0, 0, 0, 0, 0x7F, 0, 0, 0, 0, 0, 0, 0}; + ByteBuffer expected_buf = ByteBuffer.wrap(expected_raw_bytes); + assertEquals(expected_buf, buf); + + Struct1 output = Struct1.deserialize(buf); + assertEquals(0x7F, output.i); + } + + /** + * Verifies that a struct with handles cannot be serialized to a ByteBuffer. + */ + @SmallTest + public void testByteBufferSerializationWithHandles() { + StructOfNullables struct = new StructOfNullables(); + assertFalse(struct.hdl.isValid()); + assertNull(struct.struct1); + assertNull(struct.str); + + // It is okay to serialize invalid handles. + struct.serialize(); + + struct.hdl = new HandleMock(); + + try { + struct.serialize(); + fail("Serializing a struct with handles to a ByteBuffer should have thrown an " + + "exception."); + } catch (UnsupportedOperationException ex) { + // Expected. + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..84246188d3a3ca1ee84d853c9ee7340930f124aa --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java @@ -0,0 +1,237 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.base.test.util.UrlUtils; +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.test.mojom.mojo.ConformanceTestInterface; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterfaceTestHelper; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +/** + * Testing validation upon deserialization using the interfaces defined in the + * mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom file. + *

+ * One needs to pass '--test_data=bindings:{path to mojo/public/interfaces/bindings/tests/data}' to + * the test_runner script for this test to find the validation data it needs. + */ +public class ValidationTest extends MojoTestCase { + + /** + * The path where validation test data is. + */ + private static final File VALIDATION_TEST_DATA_PATH = + new File(UrlUtils.getIsolatedTestFilePath( + "mojo/public/interfaces/bindings/tests/data/validation")); + + /** + * The data needed for a validation test. + */ + private static class TestData { + public File dataFile; + public ValidationTestUtil.Data inputData; + public String expectedResult; + } + + private static class DataFileFilter implements FileFilter { + private final String mPrefix; + + public DataFileFilter(String prefix) { + this.mPrefix = prefix; + } + + @Override + public boolean accept(File pathname) { + // TODO(yzshen, qsr): skip some interface versioning tests. + if (pathname.getName().startsWith("conformance_mthd13_good_2")) { + return false; + } + return pathname.isFile() && pathname.getName().startsWith(mPrefix) + && pathname.getName().endsWith(".data"); + } + } + + private static String getStringContent(File f) throws FileNotFoundException { + try (Scanner scanner = new Scanner(f)) { + scanner.useDelimiter("\\Z"); + StringBuilder result = new StringBuilder(); + while (scanner.hasNext()) { + result.append(scanner.next()); + } + return result.toString().trim(); + } + } + + private static List getTestData(String prefix) + throws FileNotFoundException { + List results = new ArrayList(); + + // Fail if the test data is not present. + if (!VALIDATION_TEST_DATA_PATH.isDirectory()) { + fail("No test data directory found. " + + "Expected directory at: " + VALIDATION_TEST_DATA_PATH); + } + + File[] files = VALIDATION_TEST_DATA_PATH.listFiles(new DataFileFilter(prefix)); + if (files != null) { + for (File dataFile : files) { + File resultFile = new File(dataFile.getParent(), + dataFile.getName().replaceFirst("\\.data$", ".expected")); + TestData testData = new TestData(); + testData.dataFile = dataFile; + testData.inputData = ValidationTestUtil.parseData(getStringContent(dataFile)); + testData.expectedResult = getStringContent(resultFile); + results.add(testData); + } + } + return results; + } + + /** + * Runs all the test with the given prefix on the given {@link MessageReceiver}. + */ + private static void runTest(String prefix, MessageReceiver messageReceiver) + throws FileNotFoundException { + List testData = getTestData(prefix); + for (TestData test : testData) { + assertNull("Unable to read: " + test.dataFile.getName() + + ": " + test.inputData.getErrorMessage(), + test.inputData.getErrorMessage()); + List handles = new ArrayList(); + for (int i = 0; i < test.inputData.getHandlesCount(); ++i) { + handles.add(new HandleMock()); + } + Message message = new Message(test.inputData.getData(), handles); + boolean passed = messageReceiver.accept(message); + if (passed && !test.expectedResult.equals("PASS")) { + fail("Input: " + test.dataFile.getName() + + ": The message should have been refused. Expected error: " + + test.expectedResult); + } + if (!passed && test.expectedResult.equals("PASS")) { + fail("Input: " + test.dataFile.getName() + + ": The message should have been accepted."); + } + } + } + + private static class RoutingMessageReceiver implements MessageReceiver { + private final MessageReceiverWithResponder mRequest; + private final MessageReceiver mResponse; + + private RoutingMessageReceiver(MessageReceiverWithResponder request, + MessageReceiver response) { + this.mRequest = request; + this.mResponse = response; + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + try { + MessageHeader header = message.asServiceMessage().getHeader(); + if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) { + return mResponse.accept(message); + } else { + return mRequest.acceptWithResponder(message, new SinkMessageReceiver()); + } + } catch (DeserializationException e) { + return false; + } + } + + /** + * @see MessageReceiver#close() + */ + @Override + public void close() { + } + + } + + /** + * A trivial message receiver that refuses all messages it receives. + */ + private static class SinkMessageReceiver implements MessageReceiverWithResponder { + + @Override + public boolean accept(Message message) { + return true; + } + + @Override + public void close() { + } + + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + return true; + } + } + + /** + * Testing the conformance suite. + */ + @SmallTest + public void testConformance() throws FileNotFoundException { + runTest("conformance_", + ConformanceTestInterface.MANAGER.buildStub(CoreImpl.getInstance(), + ConformanceTestInterface.MANAGER.buildProxy( + CoreImpl.getInstance(), new SinkMessageReceiver()))); + } + + /** + * Testing the integration suite for message headers. + */ + @SmallTest + public void testIntegrationMessageHeader() throws FileNotFoundException { + runTest("integration_msghdr_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy(null, + new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } + + /** + * Testing the integration suite for request messages. + */ + @SmallTest + public void testIntegrationRequestMessage() throws FileNotFoundException { + runTest("integration_intf_rqst_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy(null, + new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } + + /** + * Testing the integration suite for response messages. + */ + @SmallTest + public void testIntegrationResponseMessage() throws FileNotFoundException { + runTest("integration_intf_resp_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy(null, + new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..91b993c3b63dfe197aa3f11270eccd0d0a978e7a --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java @@ -0,0 +1,68 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Utility class for testing message validation. The file format used to describe a message is + * described in The format is described in + * mojo/public/cpp/bindings/tests/validation_test_input_parser.h + */ +@JNINamespace("mojo::android") +public class ValidationTestUtil { + + /** + * Content of a '.data' file. + */ + public static class Data { + private final ByteBuffer mData; + private final int mHandlesCount; + private final String mErrorMessage; + + public ByteBuffer getData() { + return mData; + } + + public int getHandlesCount() { + return mHandlesCount; + } + + public String getErrorMessage() { + return mErrorMessage; + } + + private Data(ByteBuffer data, int handlesCount, String errorMessage) { + this.mData = data; + this.mHandlesCount = handlesCount; + this.mErrorMessage = errorMessage; + } + } + + /** + * Parse a '.data' file. + */ + public static Data parseData(String dataAsString) { + return nativeParseData(dataAsString); + } + + private static native Data nativeParseData(String dataAsString); + + @CalledByNative + private static Data buildData(ByteBuffer data, int handlesCount, String errorMessage) { + ByteBuffer copiedData = null; + if (data != null) { + copiedData = ByteBuffer.allocateDirect(data.limit()); + copiedData.order(ByteOrder.LITTLE_ENDIAN); + copiedData.put(data); + copiedData.flip(); + } + return new Data(copiedData, handlesCount, errorMessage); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java new file mode 100644 index 0000000000000000000000000000000000000000..623abc3ed24ab1389a4ce1df475ac0df5f9fe9d5 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java @@ -0,0 +1,151 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Testing {@link ValidationTestUtil}. + */ +public class ValidationTestUtilTest extends MojoTestCase { + + /** + * Check that the input parser is correct on a given input. + */ + public static void checkInputParser( + String input, boolean isInputValid, ByteBuffer expectedData, int expectedHandlesCount) { + ValidationTestUtil.Data data = ValidationTestUtil.parseData(input); + if (isInputValid) { + assertNull(data.getErrorMessage()); + assertEquals(expectedData, data.getData()); + assertEquals(expectedHandlesCount, data.getHandlesCount()); + } else { + assertNotNull(data.getErrorMessage()); + assertNull(data.getData()); + } + } + + /** + * Testing {@link ValidationTestUtil#parseData(String)}. + */ + @SmallTest + public void testCorrectMessageParsing() { + { + // Test empty input. + String input = ""; + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.LITTLE_ENDIAN); + + checkInputParser(input, true, expected, 0); + } + { + // Test input that only consists of comments and whitespaces. + String input = " \t // hello world \n\r \t// the answer is 42 "; + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.nativeOrder()); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[u1]0x10// hello world !! \n\r \t [u2]65535 \n" + + "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff"; + ByteBuffer expected = ByteBuffer.allocateDirect(17); + expected.order(ByteOrder.nativeOrder()); + expected.put((byte) 0x10); + expected.putShort((short) 65535); + expected.putInt(65536); + expected.putLong(-1); + expected.put((byte) 0); + expected.put((byte) 0xff); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40"; + ByteBuffer expected = ByteBuffer.allocateDirect(15); + expected.order(ByteOrder.nativeOrder()); + expected.putLong(-0x800); + expected.put((byte) -128); + expected.putShort((short) 0); + expected.putInt(-40); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[b]00001011 [b]10000000 // hello world\r [b]00000000"; + ByteBuffer expected = ByteBuffer.allocateDirect(3); + expected.order(ByteOrder.nativeOrder()); + expected.put((byte) 11); + expected.put((byte) 128); + expected.put((byte) 0); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[f]+.3e9 [d]-10.03"; + ByteBuffer expected = ByteBuffer.allocateDirect(12); + expected.order(ByteOrder.nativeOrder()); + expected.putFloat(+.3e9f); + expected.putDouble(-10.03); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar"; + ByteBuffer expected = ByteBuffer.allocateDirect(14); + expected.order(ByteOrder.nativeOrder()); + expected.putInt(14); + expected.put((byte) 0); + expected.putLong(9); + expected.put((byte) 0); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "// This message has handles! \n[handles]50 [u8]2"; + ByteBuffer expected = ByteBuffer.allocateDirect(8); + expected.order(ByteOrder.nativeOrder()); + expected.putLong(2); + expected.flip(); + + checkInputParser(input, true, expected, 50); + } + + // Test some failure cases. + { + String error_inputs[] = { + "/ hello world", + "[u1]x", + "[u2]-1000", + "[u1]0x100", + "[s2]-0x8001", + "[b]1", + "[b]1111111k", + "[dist4]unmatched", + "[anchr]hello [dist8]hello", + "[dist4]a [dist4]a [anchr]a", + "[dist4]a [anchr]a [dist4]a [anchr]a", + "0 [handles]50" + }; + + for (String input : error_inputs) { + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.nativeOrder()); + checkInputParser(input, false, expected, 0); + } + } + + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..8fb79d7edfb4b45aa7c0d3dc81405737cd2df776 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java @@ -0,0 +1,31 @@ +// Copyright 2014 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. + +package org.chromium.mojo.bindings.test.mojom.mojo; + +import org.chromium.mojo.bindings.MessageReceiver; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface.Method0Response; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface_Internal.IntegrationTestInterfaceMethod0ResponseParamsForwardToCallback; + +/** + * Helper class to access {@link IntegrationTestInterface_Internal} package protected method for + * tests. + */ +public class IntegrationTestInterfaceTestHelper { + + private static final class SinkMethod0Response implements Method0Response { + @Override + public void call(byte[] arg1) { + } + } + + /** + * Creates a new {@link MessageReceiver} to use for the callback of + * |IntegrationTestInterface#method0(Method0Response)|. + */ + public static MessageReceiver newIntegrationTestInterfaceMethodCallback() { + return new IntegrationTestInterfaceMethod0ResponseParamsForwardToCallback( + new SinkMethod0Response()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5120198feb90dc5f1180f5f36879a141cf14d0dd --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java @@ -0,0 +1,545 @@ +// Copyright 2014 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. + +package org.chromium.mojo.system.impl; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignals; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.InvalidHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.SharedBufferHandle; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +/** + * Testing the core API. + */ +public class CoreImplTest extends MojoTestCase { + private static final long RUN_LOOP_TIMEOUT_MS = 5; + + private static final ScheduledExecutorService WORKER = + Executors.newSingleThreadScheduledExecutor(); + + private static final HandleSignals ALL_SIGNALS = + HandleSignals.none().setPeerClosed(true).setReadable(true).setWritable(true); + + private List mHandlesToClose = new ArrayList(); + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + MojoException toThrow = null; + for (Handle handle : mHandlesToClose) { + try { + handle.close(); + } catch (MojoException e) { + if (toThrow == null) { + toThrow = e; + } + } + } + if (toThrow != null) { + throw toThrow; + } + super.tearDown(); + } + + private void addHandleToClose(Handle handle) { + mHandlesToClose.add(handle); + } + + private void addHandlePairToClose(Pair handles) { + mHandlesToClose.add(handles.first); + mHandlesToClose.add(handles.second); + } + + private static void checkSendingMessage(MessagePipeHandle in, MessagePipeHandle out) { + Random random = new Random(); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + in.writeMessage(buffer, null, MessagePipeHandle.WriteFlags.NONE); + + // Try to read into a small buffer. + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length / 2); + ResultAnd result = + out.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.RESOURCE_EXHAUSTED, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().getMessageSize()); + assertEquals(0, result.getValue().getHandlesCount()); + + // Read into a correct buffer. + receiveBuffer = ByteBuffer.allocateDirect(bytes.length); + result = out.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().getMessageSize()); + assertEquals(0, result.getValue().getHandlesCount()); + assertEquals(0, receiveBuffer.position()); + assertEquals(result.getValue().getMessageSize(), receiveBuffer.limit()); + byte[] receivedBytes = new byte[result.getValue().getMessageSize()]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals(bytes, receivedBytes)); + } + + private static void checkSendingData(DataPipe.ProducerHandle in, DataPipe.ConsumerHandle out) { + Random random = new Random(); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + ResultAnd result = in.writeData(buffer, DataPipe.WriteFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().intValue()); + + // Query number of bytes available. + ResultAnd readResult = out.readData(null, DataPipe.ReadFlags.none().query(true)); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length, readResult.getValue().intValue()); + + // Peek data into a buffer. + ByteBuffer peekBuffer = ByteBuffer.allocateDirect(bytes.length); + readResult = out.readData(peekBuffer, DataPipe.ReadFlags.none().peek(true)); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length, readResult.getValue().intValue()); + assertEquals(bytes.length, peekBuffer.limit()); + byte[] peekBytes = new byte[bytes.length]; + peekBuffer.get(peekBytes); + assertTrue(Arrays.equals(bytes, peekBytes)); + + // Read into a buffer. + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length); + readResult = out.readData(receiveBuffer, DataPipe.ReadFlags.NONE); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length, readResult.getValue().intValue()); + assertEquals(0, receiveBuffer.position()); + assertEquals(bytes.length, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals(bytes, receivedBytes)); + } + + private static void checkSharing(SharedBufferHandle in, SharedBufferHandle out) { + Random random = new Random(); + + ByteBuffer buffer1 = in.map(0, 8, SharedBufferHandle.MapFlags.NONE); + assertEquals(8, buffer1.capacity()); + ByteBuffer buffer2 = out.map(0, 8, SharedBufferHandle.MapFlags.NONE); + assertEquals(8, buffer2.capacity()); + + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + buffer1.put(bytes); + + byte[] receivedBytes = new byte[bytes.length]; + buffer2.get(receivedBytes); + + assertTrue(Arrays.equals(bytes, receivedBytes)); + + in.unmap(buffer1); + out.unmap(buffer2); + } + + /** + * Testing that Core can be retrieved from a handle. + */ + @SmallTest + public void testGetCore() { + Core core = CoreImpl.getInstance(); + + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + assertEquals(core, handles.first.getCore()); + assertEquals(core, handles.second.getCore()); + + handles = core.createDataPipe(null); + addHandlePairToClose(handles); + assertEquals(core, handles.first.getCore()); + assertEquals(core, handles.second.getCore()); + + SharedBufferHandle handle = core.createSharedBuffer(null, 100); + SharedBufferHandle handle2 = handle.duplicate(null); + addHandleToClose(handle); + addHandleToClose(handle2); + assertEquals(core, handle.getCore()); + assertEquals(core, handle2.getCore()); + } + + private static void createAndCloseMessagePipe(MessagePipeHandle.CreateOptions options) { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(options); + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link MessagePipeHandle} creation. + */ + @SmallTest + public void testMessagePipeCreation() { + // Test creation with null options. + createAndCloseMessagePipe(null); + // Test creation with default options. + createAndCloseMessagePipe(new MessagePipeHandle.CreateOptions()); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeEmpty() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + // Testing read on an empty pipe. + ResultAnd readResult = + handles.first.readMessage(null, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.SHOULD_WAIT, readResult.getMojoResult()); + + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeSend() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + checkSendingMessage(handles.first, handles.second); + checkSendingMessage(handles.second, handles.first); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeReceiveOnSmallBuffer() { + Random random = new Random(); + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + handles.first.writeMessage(buffer, null, MessagePipeHandle.WriteFlags.NONE); + + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(1); + ResultAnd result = + handles.second.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.RESOURCE_EXHAUSTED, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().getMessageSize()); + assertEquals(0, result.getValue().getHandlesCount()); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeSendHandles() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + Pair handlesToShare = core.createMessagePipe(null); + addHandlePairToClose(handles); + addHandlePairToClose(handlesToShare); + + handles.first.writeMessage(null, Collections.singletonList(handlesToShare.second), + MessagePipeHandle.WriteFlags.NONE); + assertFalse(handlesToShare.second.isValid()); + ResultAnd readMessageResult = + handles.second.readMessage(null, 1, MessagePipeHandle.ReadFlags.NONE); + assertEquals(1, readMessageResult.getValue().getHandlesCount()); + MessagePipeHandle newHandle = + readMessageResult.getValue().getHandles().get(0).toMessagePipeHandle(); + addHandleToClose(newHandle); + assertTrue(newHandle.isValid()); + checkSendingMessage(handlesToShare.first, newHandle); + checkSendingMessage(newHandle, handlesToShare.first); + } + + private static void createAndCloseDataPipe(DataPipe.CreateOptions options) { + Core core = CoreImpl.getInstance(); + Pair handles = + core.createDataPipe(options); + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeCreation() { + // Create datapipe with null options. + createAndCloseDataPipe(null); + DataPipe.CreateOptions options = new DataPipe.CreateOptions(); + // Create datapipe with element size set. + options.setElementNumBytes(24); + createAndCloseDataPipe(options); + // Create datapipe with capacity set. + options.setCapacityNumBytes(1024 * options.getElementNumBytes()); + createAndCloseDataPipe(options); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeSend() { + Core core = CoreImpl.getInstance(); + + Pair handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + checkSendingData(handles.first, handles.second); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeTwoPhaseSend() { + Random random = new Random(); + Core core = CoreImpl.getInstance(); + Pair handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = handles.first.beginWriteData(bytes.length, DataPipe.WriteFlags.NONE); + assertTrue(buffer.capacity() >= bytes.length); + buffer.put(bytes); + handles.first.endWriteData(bytes.length); + + // Read into a buffer. + ByteBuffer receiveBuffer = + handles.second.beginReadData(bytes.length, DataPipe.ReadFlags.NONE); + assertEquals(0, receiveBuffer.position()); + assertEquals(bytes.length, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals(bytes, receivedBytes)); + handles.second.endReadData(bytes.length); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeDiscard() { + Random random = new Random(); + Core core = CoreImpl.getInstance(); + Pair handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + ResultAnd result = handles.first.writeData(buffer, DataPipe.WriteFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().intValue()); + + // Discard bytes. + final int nbBytesToDiscard = 4; + assertEquals(nbBytesToDiscard, + handles.second.discardData(nbBytesToDiscard, DataPipe.ReadFlags.NONE)); + + // Read into a buffer. + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length - nbBytesToDiscard); + ResultAnd readResult = + handles.second.readData(receiveBuffer, DataPipe.ReadFlags.NONE); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length - nbBytesToDiscard, readResult.getValue().intValue()); + assertEquals(0, receiveBuffer.position()); + assertEquals(bytes.length - nbBytesToDiscard, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length - nbBytesToDiscard]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals( + Arrays.copyOfRange(bytes, nbBytesToDiscard, bytes.length), receivedBytes)); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @SmallTest + public void testSharedBufferCreation() { + Core core = CoreImpl.getInstance(); + // Test creation with empty options. + core.createSharedBuffer(null, 8).close(); + // Test creation with default options. + core.createSharedBuffer(new SharedBufferHandle.CreateOptions(), 8).close(); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @SmallTest + public void testSharedBufferDuplication() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + + // Test duplication with empty options. + handle.duplicate(null).close(); + // Test creation with default options. + handle.duplicate(new SharedBufferHandle.DuplicateOptions()).close(); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @SmallTest + public void testSharedBufferSending() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + SharedBufferHandle newHandle = handle.duplicate(null); + addHandleToClose(newHandle); + + checkSharing(handle, newHandle); + checkSharing(newHandle, handle); + } + + /** + * Testing that invalid handle can be used with this implementation. + */ + @SmallTest + public void testInvalidHandle() { + Core core = CoreImpl.getInstance(); + Handle handle = InvalidHandle.INSTANCE; + + // Checking sending an invalid handle. + // Until the behavior is changed on the C++ side, handle gracefully 2 different use case: + // - Receive a INVALID_ARGUMENT exception + // - Receive an invalid handle on the other side. + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + try { + handles.first.writeMessage(null, Collections.singletonList(handle), + MessagePipeHandle.WriteFlags.NONE); + ResultAnd readMessageResult = + handles.second.readMessage(null, 1, MessagePipeHandle.ReadFlags.NONE); + assertEquals(1, readMessageResult.getValue().getHandlesCount()); + assertFalse(readMessageResult.getValue().getHandles().get(0).isValid()); + } catch (MojoException e) { + assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult()); + } + } + + /** + * Testing the pass method on message pipes. + */ + @SmallTest + public void testMessagePipeHandlePass() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + assertTrue(handles.first.isValid()); + MessagePipeHandle handleClone = handles.first.pass(); + + addHandleToClose(handleClone); + + assertFalse(handles.first.isValid()); + assertTrue(handleClone.isValid()); + checkSendingMessage(handleClone, handles.second); + checkSendingMessage(handles.second, handleClone); + } + + /** + * Testing the pass method on data pipes. + */ + @SmallTest + public void testDataPipeHandlePass() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + DataPipe.ProducerHandle producerClone = handles.first.pass(); + DataPipe.ConsumerHandle consumerClone = handles.second.pass(); + + addHandleToClose(producerClone); + addHandleToClose(consumerClone); + + assertFalse(handles.first.isValid()); + assertFalse(handles.second.isValid()); + assertTrue(producerClone.isValid()); + assertTrue(consumerClone.isValid()); + checkSendingData(producerClone, consumerClone); + } + + /** + * Testing the pass method on shared buffers. + */ + @SmallTest + public void testSharedBufferPass() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + SharedBufferHandle newHandle = handle.duplicate(null); + addHandleToClose(newHandle); + + SharedBufferHandle handleClone = handle.pass(); + SharedBufferHandle newHandleClone = newHandle.pass(); + + addHandleToClose(handleClone); + addHandleToClose(newHandleClone); + + assertFalse(handle.isValid()); + assertTrue(handleClone.isValid()); + checkSharing(handleClone, newHandleClone); + checkSharing(newHandleClone, handleClone); + } + + /** + * esting handle conversion to native and back. + */ + @SmallTest + public void testHandleConversion() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + MessagePipeHandle converted = + core.acquireNativeHandle(handles.first.releaseNativeHandle()).toMessagePipeHandle(); + addHandleToClose(converted); + + assertFalse(handles.first.isValid()); + + checkSendingMessage(converted, handles.second); + checkSendingMessage(handles.second, converted); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java b/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e14adb1160a9b580ba80d253785480063918689e --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java @@ -0,0 +1,255 @@ +// Copyright 2016 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. + +package org.chromium.mojo.system.impl; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.InvalidHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.Watcher; +import org.chromium.mojo.system.Watcher.Callback; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * Testing the Watcher. + */ +public class WatcherImplTest extends MojoTestCase { + private List mHandlesToClose = new ArrayList(); + private Watcher mWatcher; + private Core mCore; + + /** + * @see MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + mWatcher = new WatcherImpl(); + mCore = CoreImpl.getInstance(); + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + mWatcher.destroy(); + MojoException toThrow = null; + for (Handle handle : mHandlesToClose) { + try { + handle.close(); + } catch (MojoException e) { + if (toThrow == null) { + toThrow = e; + } + } + } + if (toThrow != null) { + throw toThrow; + } + super.tearDown(); + } + + private void addHandlePairToClose(Pair handles) { + mHandlesToClose.add(handles.first); + mHandlesToClose.add(handles.second); + } + + private static class WatcherResult implements Callback { + private int mResult = Integer.MIN_VALUE; + private MessagePipeHandle mReadPipe; + + /** + * @param readPipe A MessagePipeHandle to read from when onResult triggers success. + */ + public WatcherResult(MessagePipeHandle readPipe) { + mReadPipe = readPipe; + } + public WatcherResult() { + this(null); + } + + /** + * @see Callback#onResult(int) + */ + @Override + public void onResult(int result) { + this.mResult = result; + + if (result == MojoResult.OK && mReadPipe != null) { + mReadPipe.readMessage( + null, 0, MessagePipeHandle.ReadFlags.none().setMayDiscard(true)); + } + } + + /** + * @return the result + */ + public int getResult() { + return mResult; + } + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testCorrectResult() { + // Checking a correct result. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + final WatcherResult watcherResult = new WatcherResult(handles.first); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.writeMessage( + ByteBuffer.allocateDirect(1), null, MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + assertEquals(MojoResult.OK, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testClosingPeerHandle() { + // Closing the peer handle. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.close(); + runLoopUntilIdle(); + assertEquals(MojoResult.FAILED_PRECONDITION, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testClosingWatchedHandle() { + // Closing the peer handle. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.first.close(); + runLoopUntilIdle(); + assertEquals(MojoResult.CANCELLED, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testInvalidHandle() { + // Closing the peer handle. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.first.close(); + assertEquals(MojoResult.INVALID_ARGUMENT, + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult)); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testDefaultInvalidHandle() { + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + assertEquals(MojoResult.INVALID_ARGUMENT, + mWatcher.start(InvalidHandle.INSTANCE, Core.HandleSignals.READABLE, watcherResult)); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testCancel() { + // Closing the peer handle. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.cancel(); + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.writeMessage( + ByteBuffer.allocateDirect(1), null, MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testImmediateCancelOnInvalidHandle() { + // Closing the peer handle. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + handles.first.close(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + mWatcher.cancel(); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } +} diff --git a/mojo/android/javatests/validation_test_util.cc b/mojo/android/javatests/validation_test_util.cc new file mode 100644 index 0000000000000000000000000000000000000000..75f79b370edc16c6f7348ebe9a04d5ae1292f991 --- /dev/null +++ b/mojo/android/javatests/validation_test_util.cc @@ -0,0 +1,54 @@ +// Copyright 2014 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. + +#include "mojo/android/javatests/validation_test_util.h" + +#include +#include + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/test/test_support_android.h" +#include "jni/ValidationTestUtil_jni.h" +#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h" + +using base::android::JavaParamRef; +using base::android::ScopedJavaLocalRef; + +namespace mojo { +namespace android { + +bool RegisterValidationTestUtil(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +ScopedJavaLocalRef ParseData( + JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& data_as_string) { + std::string input = + base::android::ConvertJavaStringToUTF8(env, data_as_string); + std::vector data; + size_t num_handles; + std::string error_message; + if (!test::ParseValidationTestInput( + input, &data, &num_handles, &error_message)) { + ScopedJavaLocalRef j_error_message = + base::android::ConvertUTF8ToJavaString(env, error_message); + return Java_ValidationTestUtil_buildData(env, nullptr, 0, j_error_message); + } + void* data_ptr = &data[0]; + if (!data_ptr) { + DCHECK(!data.size()); + data_ptr = &data; + } + jobject byte_buffer = + env->NewDirectByteBuffer(data_ptr, data.size()); + return Java_ValidationTestUtil_buildData(env, byte_buffer, num_handles, + nullptr); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/javatests/validation_test_util.h b/mojo/android/javatests/validation_test_util.h new file mode 100644 index 0000000000000000000000000000000000000000..f58dc07885f7b948e171745daa67cf5f569af811 --- /dev/null +++ b/mojo/android/javatests/validation_test_util.h @@ -0,0 +1,20 @@ +// Copyright 2014 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 MOJO_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_ +#define MOJO_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_ + +#include + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterValidationTestUtil(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_SYSTEM_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_ diff --git a/mojo/android/system/base_run_loop.cc b/mojo/android/system/base_run_loop.cc new file mode 100644 index 0000000000000000000000000000000000000000..7993ba86a398a5c42db9acf07a01014936c79cb6 --- /dev/null +++ b/mojo/android/system/base_run_loop.cc @@ -0,0 +1,82 @@ +// 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. + +#include "mojo/android/system/base_run_loop.h" + +#include + +// Removed unused headers. TODO(hidehiko): Upstream. +// #include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +// #include "base/android/jni_registrar.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "jni/BaseRunLoop_jni.h" + +using base::android::JavaParamRef; + +namespace mojo { +namespace android { + +static jlong CreateBaseRunLoop(JNIEnv* env, + const JavaParamRef& jcaller) { + base::MessageLoop* message_loop = new base::MessageLoop; + return reinterpret_cast(message_loop); +} + +static void Run(JNIEnv* env, + const JavaParamRef& jcaller) { + base::RunLoop().Run(); +} + +static void RunUntilIdle(JNIEnv* env, + const JavaParamRef& jcaller) { + base::RunLoop().RunUntilIdle(); +} + +static void Quit(JNIEnv* env, + const JavaParamRef& jcaller, + jlong runLoopID) { + reinterpret_cast(runLoopID)->QuitWhenIdle(); +} + +static void RunJavaRunnable( + const base::android::ScopedJavaGlobalRef& runnable_ref) { + Java_BaseRunLoop_runRunnable(base::android::AttachCurrentThread(), + runnable_ref); +} + +static void PostDelayedTask(JNIEnv* env, + const JavaParamRef& jcaller, + jlong runLoopID, + const JavaParamRef& runnable, + jlong delay) { + base::android::ScopedJavaGlobalRef runnable_ref; + // ScopedJavaGlobalRef do not hold onto the env reference, so it is safe to + // use it across threads. |RunJavaRunnable| will acquire a new JNIEnv before + // running the Runnable. + runnable_ref.Reset(env, runnable); + reinterpret_cast(runLoopID) + ->task_runner() + ->PostDelayedTask(FROM_HERE, base::Bind(&RunJavaRunnable, runnable_ref), + base::TimeDelta::FromMicroseconds(delay)); +} + +static void DeleteMessageLoop(JNIEnv* env, + const JavaParamRef& jcaller, + jlong runLoopID) { + base::MessageLoop* message_loop = + reinterpret_cast(runLoopID); + delete message_loop; +} + +bool RegisterBaseRunLoop(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/system/base_run_loop.h b/mojo/android/system/base_run_loop.h new file mode 100644 index 0000000000000000000000000000000000000000..f225c65375b09be0fbdace3a8fd429a55c5dde06 --- /dev/null +++ b/mojo/android/system/base_run_loop.h @@ -0,0 +1,20 @@ +// 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. + +#ifndef MOJO_ANDROID_SYSTEM_BASE_RUN_LOOP_H_ +#define MOJO_ANDROID_SYSTEM_BASE_RUN_LOOP_H_ + +#include + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterBaseRunLoop(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_ANDROID_SYSTEM_BASE_RUN_LOOP_H_ diff --git a/mojo/android/system/core_impl.cc b/mojo/android/system/core_impl.cc new file mode 100644 index 0000000000000000000000000000000000000000..7d5a40220dca388053f86a300d0495fbe1dc44b7 --- /dev/null +++ b/mojo/android/system/core_impl.cc @@ -0,0 +1,310 @@ +// Copyright 2014 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. + +#include "mojo/android/system/core_impl.h" + +#include +#include + +// Removed unused headers. TODO(hidehiko): Upstream. +// #include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +// #include "base/android/jni_registrar.h" +// #include "base/android/library_loader/library_loader_hooks.h" +#include "base/android/scoped_java_ref.h" +#include "jni/CoreImpl_jni.h" +#include "mojo/public/c/system/core.h" + +namespace mojo { +namespace android { + +using base::android::JavaParamRef; +using base::android::ScopedJavaLocalRef; + +static jlong GetTimeTicksNow(JNIEnv* env, + const JavaParamRef& jcaller) { + return MojoGetTimeTicksNow(); +} + +static ScopedJavaLocalRef CreateMessagePipe( + JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& options_buffer) { + const MojoCreateMessagePipeOptions* options = NULL; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + DCHECK_EQ(reinterpret_cast(buffer_start) % 8, 0u); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoCreateMessagePipeOptions)); + options = static_cast(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle1; + MojoHandle handle2; + MojoResult result = MojoCreateMessagePipe(options, &handle1, &handle2); + return Java_CoreImpl_newNativeCreationResult(env, result, handle1, handle2); +} + +static ScopedJavaLocalRef CreateDataPipe( + JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& options_buffer) { + const MojoCreateDataPipeOptions* options = NULL; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + DCHECK_EQ(reinterpret_cast(buffer_start) % 8, 0u); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoCreateDataPipeOptions)); + options = static_cast(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle1; + MojoHandle handle2; + MojoResult result = MojoCreateDataPipe(options, &handle1, &handle2); + return Java_CoreImpl_newNativeCreationResult(env, result, handle1, handle2); +} + +static ScopedJavaLocalRef CreateSharedBuffer( + JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& options_buffer, + jlong num_bytes) { + const MojoCreateSharedBufferOptions* options = 0; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + DCHECK_EQ(reinterpret_cast(buffer_start) % 8, 0u); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoCreateSharedBufferOptions)); + options = static_cast(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle; + MojoResult result = MojoCreateSharedBuffer(options, num_bytes, &handle); + return Java_CoreImpl_newResultAndInteger(env, result, handle); +} + +static jint Close(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle) { + return MojoClose(mojo_handle); +} + +static jint QueryHandleSignalsState(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& buffer) { + MojoHandleSignalsState* signals_state = + static_cast(env->GetDirectBufferAddress(buffer)); + DCHECK(signals_state); + DCHECK_EQ(sizeof(MojoHandleSignalsState), + static_cast(env->GetDirectBufferCapacity(buffer))); + return MojoQueryHandleSignalsState(mojo_handle, signals_state); +} + +static jint WriteMessage(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& bytes, + jint num_bytes, + const JavaParamRef& handles_buffer, + jint flags) { + const void* buffer_start = 0; + uint32_t buffer_size = 0; + if (bytes) { + buffer_start = env->GetDirectBufferAddress(bytes); + DCHECK(buffer_start); + DCHECK(env->GetDirectBufferCapacity(bytes) >= num_bytes); + buffer_size = num_bytes; + } + const MojoHandle* handles = 0; + uint32_t num_handles = 0; + if (handles_buffer) { + handles = + static_cast(env->GetDirectBufferAddress(handles_buffer)); + num_handles = env->GetDirectBufferCapacity(handles_buffer) / 4; + } + // Java code will handle invalidating handles if the write succeeded. + return MojoWriteMessage( + mojo_handle, buffer_start, buffer_size, handles, num_handles, flags); +} + +static ScopedJavaLocalRef ReadMessage( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& bytes, + const JavaParamRef& handles_buffer, + jint flags) { + void* buffer_start = 0; + uint32_t buffer_size = 0; + if (bytes) { + buffer_start = env->GetDirectBufferAddress(bytes); + DCHECK(buffer_start); + buffer_size = env->GetDirectBufferCapacity(bytes); + } + MojoHandle* handles = 0; + uint32_t num_handles = 0; + if (handles_buffer) { + handles = + static_cast(env->GetDirectBufferAddress(handles_buffer)); + num_handles = env->GetDirectBufferCapacity(handles_buffer) / 4; + } + MojoResult result = MojoReadMessage( + mojo_handle, buffer_start, &buffer_size, handles, &num_handles, flags); + // Jave code will handle taking ownership of any received handle. + return Java_CoreImpl_newReadMessageResult(env, result, buffer_size, + num_handles); +} + +static ScopedJavaLocalRef ReadData( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& elements, + jint elements_capacity, + jint flags) { + void* buffer_start = 0; + uint32_t buffer_size = elements_capacity; + if (elements) { + buffer_start = env->GetDirectBufferAddress(elements); + DCHECK(buffer_start); + DCHECK(elements_capacity <= env->GetDirectBufferCapacity(elements)); + } + MojoResult result = + MojoReadData(mojo_handle, buffer_start, &buffer_size, flags); + return Java_CoreImpl_newResultAndInteger( + env, result, (result == MOJO_RESULT_OK) ? buffer_size : 0); +} + +static ScopedJavaLocalRef BeginReadData( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jint num_bytes, + jint flags) { + void const* buffer = 0; + uint32_t buffer_size = num_bytes; + MojoResult result = + MojoBeginReadData(mojo_handle, &buffer, &buffer_size, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = + env->NewDirectByteBuffer(const_cast(buffer), buffer_size); + } + return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer); +} + +static jint EndReadData(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jint num_bytes_read) { + return MojoEndReadData(mojo_handle, num_bytes_read); +} + +static ScopedJavaLocalRef WriteData( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& elements, + jint limit, + jint flags) { + void* buffer_start = env->GetDirectBufferAddress(elements); + DCHECK(buffer_start); + DCHECK(limit <= env->GetDirectBufferCapacity(elements)); + uint32_t buffer_size = limit; + MojoResult result = + MojoWriteData(mojo_handle, buffer_start, &buffer_size, flags); + return Java_CoreImpl_newResultAndInteger( + env, result, (result == MOJO_RESULT_OK) ? buffer_size : 0); +} + +static ScopedJavaLocalRef BeginWriteData( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jint num_bytes, + jint flags) { + void* buffer = 0; + uint32_t buffer_size = num_bytes; + MojoResult result = + MojoBeginWriteData(mojo_handle, &buffer, &buffer_size, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = env->NewDirectByteBuffer(buffer, buffer_size); + } + return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer); +} + +static jint EndWriteData(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jint num_bytes_written) { + return MojoEndWriteData(mojo_handle, num_bytes_written); +} + +static ScopedJavaLocalRef Duplicate( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& options_buffer) { + const MojoDuplicateBufferHandleOptions* options = 0; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoDuplicateBufferHandleOptions)); + options = + static_cast(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle; + MojoResult result = MojoDuplicateBufferHandle(mojo_handle, options, &handle); + return Java_CoreImpl_newResultAndInteger(env, result, handle); +} + +static ScopedJavaLocalRef Map(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jlong offset, + jlong num_bytes, + jint flags) { + void* buffer = 0; + MojoResult result = + MojoMapBuffer(mojo_handle, offset, num_bytes, &buffer, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = env->NewDirectByteBuffer(buffer, num_bytes); + } + return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer); +} + +static int Unmap(JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& buffer) { + void* buffer_start = env->GetDirectBufferAddress(buffer); + DCHECK(buffer_start); + return MojoUnmapBuffer(buffer_start); +} + +static jint GetNativeBufferOffset(JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& buffer, + jint alignment) { + jint offset = + reinterpret_cast(env->GetDirectBufferAddress(buffer)) % + alignment; + if (offset == 0) + return 0; + return alignment - offset; +} + +bool RegisterCoreImpl(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/system/core_impl.h b/mojo/android/system/core_impl.h new file mode 100644 index 0000000000000000000000000000000000000000..c6249994e5cb1f0eeffb9531fb05bfc22aff8bb3 --- /dev/null +++ b/mojo/android/system/core_impl.h @@ -0,0 +1,20 @@ +// Copyright 2014 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 MOJO_ANDROID_SYSTEM_CORE_IMPL_H_ +#define MOJO_ANDROID_SYSTEM_CORE_IMPL_H_ + +#include + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterCoreImpl(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_ANDROID_SYSTEM_CORE_IMPL_H_ diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java b/mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java new file mode 100644 index 0000000000000000000000000000000000000000..3db6670d71478fec7483aea7cd84b45dff3734a5 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java @@ -0,0 +1,74 @@ +// 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.mojo.system.RunLoop; + +/** + * Implementation of {@link RunLoop} suitable for the base:: message loop implementation. + */ +@JNINamespace("mojo::android") +class BaseRunLoop implements RunLoop { + /** + * Pointer to the C run loop. + */ + private long mRunLoopID; + private final CoreImpl mCore; + + BaseRunLoop(CoreImpl core) { + this.mCore = core; + this.mRunLoopID = nativeCreateBaseRunLoop(); + } + + @Override + public void run() { + assert mRunLoopID != 0 : "The run loop cannot run once closed"; + nativeRun(); + } + + @Override + public void runUntilIdle() { + assert mRunLoopID != 0 : "The run loop cannot run once closed"; + nativeRunUntilIdle(); + } + + @Override + public void quit() { + assert mRunLoopID != 0 : "The run loop cannot be quitted run once closed"; + nativeQuit(mRunLoopID); + } + + @Override + public void postDelayedTask(Runnable runnable, long delay) { + assert mRunLoopID != 0 : "The run loop cannot run tasks once closed"; + nativePostDelayedTask(mRunLoopID, runnable, delay); + } + + @Override + public void close() { + if (mRunLoopID == 0) { + return; + } + // We don't want to de-register a different run loop! + assert mCore.getCurrentRunLoop() == this : "Only the current run loop can be closed"; + mCore.clearCurrentRunLoop(); + nativeDeleteMessageLoop(mRunLoopID); + mRunLoopID = 0; + } + + @CalledByNative + private static void runRunnable(Runnable runnable) { + runnable.run(); + } + + private native long nativeCreateBaseRunLoop(); + private native void nativeRun(); + private native void nativeRunUntilIdle(); + private native void nativeQuit(long runLoopID); + private native void nativePostDelayedTask(long runLoopID, Runnable runnable, long delay); + private native void nativeDeleteMessageLoop(long runLoopID); +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..173f80180f6639f25ef85991a409cb4ada75a49c --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java @@ -0,0 +1,522 @@ +// Copyright 2014 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.RunLoop; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.SharedBufferHandle.DuplicateOptions; +import org.chromium.mojo.system.SharedBufferHandle.MapFlags; +import org.chromium.mojo.system.UntypedHandle; +import org.chromium.mojo.system.Watcher; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of {@link Core}. + */ +@JNINamespace("mojo::android") +@MainDex +public class CoreImpl implements Core { + /** + * Discard flag for the |MojoReadData| operation. + */ + private static final int MOJO_READ_DATA_FLAG_DISCARD = 1 << 1; + + /** + * the size of a handle, in bytes. + */ + private static final int HANDLE_SIZE = 4; + + /** + * the size of a flag, in bytes. + */ + private static final int FLAG_SIZE = 4; + + /** + * The mojo handle for an invalid handle. + */ + static final int INVALID_HANDLE = 0; + + private static class LazyHolder { private static final Core INSTANCE = new CoreImpl(); } + + /** + * The run loop for the current thread. + */ + private final ThreadLocal mCurrentRunLoop = new ThreadLocal(); + + /** + * The offset needed to get an aligned buffer. + */ + private final int mByteBufferOffset; + + /** + * @return the instance. + */ + public static Core getInstance() { + return LazyHolder.INSTANCE; + } + + private CoreImpl() { + // Fix for the ART runtime, before: + // https://android.googlesource.com/platform/libcore/+/fb6c80875a8a8d0a9628562f89c250b6a962e824%5E!/ + // This assumes consistent allocation. + mByteBufferOffset = nativeGetNativeBufferOffset(ByteBuffer.allocateDirect(8), 8); + } + + /** + * @see Core#getTimeTicksNow() + */ + @Override + public long getTimeTicksNow() { + return nativeGetTimeTicksNow(); + } + + /** + * @see Core#createMessagePipe(MessagePipeHandle.CreateOptions) + */ + @Override + public Pair createMessagePipe( + MessagePipeHandle.CreateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd result = nativeCreateMessagePipe(optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return Pair.create( + new MessagePipeHandleImpl(this, result.getValue().first), + new MessagePipeHandleImpl(this, result.getValue().second)); + } + + /** + * @see Core#createDataPipe(DataPipe.CreateOptions) + */ + @Override + public Pair createDataPipe(DataPipe.CreateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(16); + optionsBuffer.putInt(0, 16); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + optionsBuffer.putInt(8, options.getElementNumBytes()); + optionsBuffer.putInt(12, options.getCapacityNumBytes()); + } + ResultAnd result = nativeCreateDataPipe(optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return Pair.create( + new DataPipeProducerHandleImpl(this, result.getValue().first), + new DataPipeConsumerHandleImpl(this, result.getValue().second)); + } + + /** + * @see Core#createSharedBuffer(SharedBufferHandle.CreateOptions, long) + */ + @Override + public SharedBufferHandle createSharedBuffer( + SharedBufferHandle.CreateOptions options, long numBytes) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd result = nativeCreateSharedBuffer(optionsBuffer, numBytes); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return new SharedBufferHandleImpl(this, result.getValue()); + } + + /** + * @see org.chromium.mojo.system.Core#acquireNativeHandle(int) + */ + @Override + public UntypedHandle acquireNativeHandle(int handle) { + return new UntypedHandleImpl(this, handle); + } + + /** + * @see Core#getWatcher() + */ + @Override + public Watcher getWatcher() { + return new WatcherImpl(); + } + + /** + * @see Core#createDefaultRunLoop() + */ + @Override + public RunLoop createDefaultRunLoop() { + if (mCurrentRunLoop.get() != null) { + throw new MojoException(MojoResult.FAILED_PRECONDITION); + } + BaseRunLoop runLoop = new BaseRunLoop(this); + mCurrentRunLoop.set(runLoop); + return runLoop; + } + + /** + * @see Core#getCurrentRunLoop() + */ + @Override + public RunLoop getCurrentRunLoop() { + return mCurrentRunLoop.get(); + } + + /** + * Remove the current run loop. + */ + void clearCurrentRunLoop() { + mCurrentRunLoop.remove(); + } + + int closeWithResult(int mojoHandle) { + return nativeClose(mojoHandle); + } + + void close(int mojoHandle) { + int mojoResult = nativeClose(mojoHandle); + if (mojoResult != MojoResult.OK) { + throw new MojoException(mojoResult); + } + } + + HandleSignalsState queryHandleSignalsState(int mojoHandle) { + ByteBuffer buffer = allocateDirectBuffer(8); + int result = nativeQueryHandleSignalsState(mojoHandle, buffer); + if (result != MojoResult.OK) throw new MojoException(result); + return new HandleSignalsState( + new HandleSignals(buffer.getInt(0)), new HandleSignals(buffer.getInt(4))); + } + + /** + * @see MessagePipeHandle#writeMessage(ByteBuffer, List, MessagePipeHandle.WriteFlags) + */ + void writeMessage(MessagePipeHandleImpl pipeHandle, ByteBuffer bytes, + List handles, MessagePipeHandle.WriteFlags flags) { + ByteBuffer handlesBuffer = null; + if (handles != null && !handles.isEmpty()) { + handlesBuffer = allocateDirectBuffer(handles.size() * HANDLE_SIZE); + for (Handle handle : handles) { + handlesBuffer.putInt(getMojoHandle(handle)); + } + handlesBuffer.position(0); + } + int mojoResult = nativeWriteMessage(pipeHandle.getMojoHandle(), bytes, + bytes == null ? 0 : bytes.limit(), handlesBuffer, flags.getFlags()); + if (mojoResult != MojoResult.OK) { + throw new MojoException(mojoResult); + } + // Success means the handles have been invalidated. + if (handles != null) { + for (Handle handle : handles) { + if (handle.isValid()) { + ((HandleBase) handle).invalidateHandle(); + } + } + } + } + + /** + * @see MessagePipeHandle#readMessage(ByteBuffer, int, MessagePipeHandle.ReadFlags) + */ + ResultAnd readMessage(MessagePipeHandleImpl handle, + ByteBuffer bytes, int maxNumberOfHandles, MessagePipeHandle.ReadFlags flags) { + ByteBuffer handlesBuffer = null; + if (maxNumberOfHandles > 0) { + handlesBuffer = allocateDirectBuffer(maxNumberOfHandles * HANDLE_SIZE); + } + ResultAnd result = + nativeReadMessage(handle.getMojoHandle(), bytes, handlesBuffer, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK + && result.getMojoResult() != MojoResult.RESOURCE_EXHAUSTED + && result.getMojoResult() != MojoResult.SHOULD_WAIT) { + throw new MojoException(result.getMojoResult()); + } + + if (result.getMojoResult() == MojoResult.OK) { + MessagePipeHandle.ReadMessageResult readResult = result.getValue(); + if (bytes != null) { + bytes.position(0); + bytes.limit(readResult.getMessageSize()); + } + + List handles = + new ArrayList(readResult.getHandlesCount()); + for (int i = 0; i < readResult.getHandlesCount(); ++i) { + int mojoHandle = handlesBuffer.getInt(HANDLE_SIZE * i); + handles.add(new UntypedHandleImpl(this, mojoHandle)); + } + readResult.setHandles(handles); + } + return result; + } + + /** + * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags) + */ + int discardData(DataPipeConsumerHandleImpl handle, int numBytes, DataPipe.ReadFlags flags) { + ResultAnd result = nativeReadData(handle.getMojoHandle(), null, numBytes, + flags.getFlags() | MOJO_READ_DATA_FLAG_DISCARD); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see ConsumerHandle#readData(ByteBuffer, DataPipe.ReadFlags) + */ + ResultAnd readData( + DataPipeConsumerHandleImpl handle, ByteBuffer elements, DataPipe.ReadFlags flags) { + ResultAnd result = nativeReadData(handle.getMojoHandle(), elements, + elements == null ? 0 : elements.capacity(), flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK + && result.getMojoResult() != MojoResult.SHOULD_WAIT) { + throw new MojoException(result.getMojoResult()); + } + if (result.getMojoResult() == MojoResult.OK) { + if (elements != null) { + elements.limit(result.getValue()); + } + } + return result; + } + + /** + * @see ConsumerHandle#beginReadData(int, DataPipe.ReadFlags) + */ + ByteBuffer beginReadData( + DataPipeConsumerHandleImpl handle, int numBytes, DataPipe.ReadFlags flags) { + ResultAnd result = + nativeBeginReadData(handle.getMojoHandle(), numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue().asReadOnlyBuffer(); + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + void endReadData(DataPipeConsumerHandleImpl handle, int numBytesRead) { + int result = nativeEndReadData(handle.getMojoHandle(), numBytesRead); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @see ProducerHandle#writeData(ByteBuffer, DataPipe.WriteFlags) + */ + ResultAnd writeData( + DataPipeProducerHandleImpl handle, ByteBuffer elements, DataPipe.WriteFlags flags) { + return nativeWriteData( + handle.getMojoHandle(), elements, elements.limit(), flags.getFlags()); + } + + /** + * @see ProducerHandle#beginWriteData(int, DataPipe.WriteFlags) + */ + ByteBuffer beginWriteData( + DataPipeProducerHandleImpl handle, int numBytes, DataPipe.WriteFlags flags) { + ResultAnd result = + nativeBeginWriteData(handle.getMojoHandle(), numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + void endWriteData(DataPipeProducerHandleImpl handle, int numBytesWritten) { + int result = nativeEndWriteData(handle.getMojoHandle(), numBytesWritten); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @see SharedBufferHandle#duplicate(DuplicateOptions) + */ + SharedBufferHandle duplicate(SharedBufferHandleImpl handle, DuplicateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd result = nativeDuplicate(handle.getMojoHandle(), optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return new SharedBufferHandleImpl(this, result.getValue()); + } + + /** + * @see SharedBufferHandle#map(long, long, MapFlags) + */ + ByteBuffer map(SharedBufferHandleImpl handle, long offset, long numBytes, MapFlags flags) { + ResultAnd result = + nativeMap(handle.getMojoHandle(), offset, numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see SharedBufferHandle#unmap(ByteBuffer) + */ + void unmap(ByteBuffer buffer) { + int result = nativeUnmap(buffer); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @return the mojo handle associated to the given handle, considering invalid handles. + */ + private int getMojoHandle(Handle handle) { + if (handle.isValid()) { + return ((HandleBase) handle).getMojoHandle(); + } + return 0; + } + + private static boolean isUnrecoverableError(int code) { + switch (code) { + case MojoResult.OK: + case MojoResult.DEADLINE_EXCEEDED: + case MojoResult.CANCELLED: + case MojoResult.FAILED_PRECONDITION: + return false; + default: + return true; + } + } + + private static int filterMojoResultForWait(int code) { + if (isUnrecoverableError(code)) { + throw new MojoException(code); + } + return code; + } + + private ByteBuffer allocateDirectBuffer(int capacity) { + ByteBuffer buffer = ByteBuffer.allocateDirect(capacity + mByteBufferOffset); + if (mByteBufferOffset != 0) { + buffer.position(mByteBufferOffset); + buffer = buffer.slice(); + } + return buffer.order(ByteOrder.nativeOrder()); + } + + @CalledByNative + private static ResultAnd newResultAndBuffer(int mojoResult, ByteBuffer buffer) { + return new ResultAnd<>(mojoResult, buffer); + } + + /** + * Trivial alias for Pair. This is needed because our jni generator is unable + * to handle class that contains space. + */ + private static final class IntegerPair extends Pair { + public IntegerPair(Integer first, Integer second) { + super(first, second); + } + } + + @CalledByNative + private static ResultAnd newReadMessageResult( + int mojoResult, int messageSize, int handlesCount) { + MessagePipeHandle.ReadMessageResult result = new MessagePipeHandle.ReadMessageResult(); + result.setMessageSize(messageSize); + result.setHandlesCount(handlesCount); + return new ResultAnd<>(mojoResult, result); + } + + @CalledByNative + private static ResultAnd newResultAndInteger(int mojoResult, int numBytesRead) { + return new ResultAnd<>(mojoResult, numBytesRead); + } + + @CalledByNative + private static ResultAnd newNativeCreationResult( + int mojoResult, int mojoHandle1, int mojoHandle2) { + return new ResultAnd<>(mojoResult, new IntegerPair(mojoHandle1, mojoHandle2)); + } + + private native long nativeGetTimeTicksNow(); + + private native ResultAnd nativeCreateMessagePipe(ByteBuffer optionsBuffer); + + private native ResultAnd nativeCreateDataPipe(ByteBuffer optionsBuffer); + + private native ResultAnd nativeCreateSharedBuffer( + ByteBuffer optionsBuffer, long numBytes); + + private native int nativeClose(int mojoHandle); + + private native int nativeQueryHandleSignalsState(int mojoHandle, ByteBuffer signalsStateBuffer); + + private native int nativeWriteMessage( + int mojoHandle, ByteBuffer bytes, int numBytes, ByteBuffer handlesBuffer, int flags); + + private native ResultAnd nativeReadMessage( + int mojoHandle, ByteBuffer bytes, ByteBuffer handlesBuffer, int flags); + + private native ResultAnd nativeReadData( + int mojoHandle, ByteBuffer elements, int elementsSize, int flags); + + private native ResultAnd nativeBeginReadData( + int mojoHandle, int numBytes, int flags); + + private native int nativeEndReadData(int mojoHandle, int numBytesRead); + + private native ResultAnd nativeWriteData( + int mojoHandle, ByteBuffer elements, int limit, int flags); + + private native ResultAnd nativeBeginWriteData( + int mojoHandle, int numBytes, int flags); + + private native int nativeEndWriteData(int mojoHandle, int numBytesWritten); + + private native ResultAnd nativeDuplicate(int mojoHandle, ByteBuffer optionsBuffer); + + private native ResultAnd nativeMap( + int mojoHandle, long offset, long numBytes, int flags); + + private native int nativeUnmap(ByteBuffer buffer); + + private native int nativeGetNativeBufferOffset(ByteBuffer buffer, int alignment); +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..83097d7eb467e8042bbf6430cc711bae0978d5e5 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java @@ -0,0 +1,72 @@ +// Copyright 2014 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ReadFlags; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link ConsumerHandle}. + */ +class DataPipeConsumerHandleImpl extends HandleBase implements ConsumerHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + DataPipeConsumerHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + DataPipeConsumerHandleImpl(HandleBase other) { + super(other); + } + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public ConsumerHandle pass() { + return new DataPipeConsumerHandleImpl(this); + } + + /** + * @see ConsumerHandle#discardData(int, ReadFlags) + */ + @Override + public int discardData(int numBytes, ReadFlags flags) { + return mCore.discardData(this, numBytes, flags); + } + + /** + * @see ConsumerHandle#readData(ByteBuffer, ReadFlags) + */ + @Override + public ResultAnd readData(ByteBuffer elements, ReadFlags flags) { + return mCore.readData(this, elements, flags); + } + + /** + * @see ConsumerHandle#beginReadData(int, ReadFlags) + */ + @Override + public ByteBuffer beginReadData(int numBytes, ReadFlags flags) { + return mCore.beginReadData(this, numBytes, flags); + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + @Override + public void endReadData(int numBytesRead) { + mCore.endReadData(this, numBytesRead); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..901f26c02920af10b38c73a2ab40690bd0f28acf --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java @@ -0,0 +1,64 @@ +// Copyright 2014 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.DataPipe.WriteFlags; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link ProducerHandle}. + */ +class DataPipeProducerHandleImpl extends HandleBase implements ProducerHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + DataPipeProducerHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + DataPipeProducerHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.DataPipe.ProducerHandle#pass() + */ + @Override + public ProducerHandle pass() { + return new DataPipeProducerHandleImpl(this); + } + + /** + * @see ProducerHandle#writeData(ByteBuffer, WriteFlags) + */ + @Override + public ResultAnd writeData(ByteBuffer elements, WriteFlags flags) { + return mCore.writeData(this, elements, flags); + } + + /** + * @see ProducerHandle#beginWriteData(int, WriteFlags) + */ + @Override + public ByteBuffer beginWriteData(int numBytes, WriteFlags flags) { + return mCore.beginWriteData(this, numBytes, flags); + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + @Override + public void endWriteData(int numBytesWritten) { + mCore.endWriteData(this, numBytesWritten); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java new file mode 100644 index 0000000000000000000000000000000000000000..4d149a48d715d43134f1aef09a0624f4fb4021bf --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java @@ -0,0 +1,140 @@ +// Copyright 2014 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. + +package org.chromium.mojo.system.impl; + +import android.util.Log; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.UntypedHandle; + +/** + * Implementation of {@link Handle}. + */ +abstract class HandleBase implements Handle { + + private static final String TAG = "HandleImpl"; + + /** + * The pointer to the scoped handle owned by this object. + */ + private int mMojoHandle; + + /** + * The core implementation. Will be used to delegate all behavior. + */ + protected CoreImpl mCore; + + /** + * Base constructor. Takes ownership of the passed handle. + */ + HandleBase(CoreImpl core, int mojoHandle) { + mCore = core; + mMojoHandle = mojoHandle; + } + + /** + * Constructor for transforming {@link HandleBase} into a specific one. It is used to transform + * an {@link UntypedHandle} into a typed one, or any handle into an {@link UntypedHandle}. + */ + protected HandleBase(HandleBase other) { + mCore = other.mCore; + HandleBase otherAsHandleImpl = other; + int mojoHandle = otherAsHandleImpl.mMojoHandle; + otherAsHandleImpl.mMojoHandle = CoreImpl.INVALID_HANDLE; + mMojoHandle = mojoHandle; + } + + /** + * @see org.chromium.mojo.system.Handle#close() + */ + @Override + public void close() { + if (mMojoHandle != CoreImpl.INVALID_HANDLE) { + // After a close, the handle is invalid whether the close succeed or not. + int handle = mMojoHandle; + mMojoHandle = CoreImpl.INVALID_HANDLE; + mCore.close(handle); + } + } + + /** + * @see org.chromium.mojo.system.Handle#querySignalsState() + */ + @Override + public HandleSignalsState querySignalsState() { + return mCore.queryHandleSignalsState(mMojoHandle); + } + + /** + * @see org.chromium.mojo.system.Handle#isValid() + */ + @Override + public boolean isValid() { + return mMojoHandle != CoreImpl.INVALID_HANDLE; + } + + /** + * @see org.chromium.mojo.system.Handle#toUntypedHandle() + */ + @Override + public UntypedHandle toUntypedHandle() { + return new UntypedHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.Handle#getCore() + */ + @Override + public Core getCore() { + return mCore; + } + + /** + * @see Handle#releaseNativeHandle() + */ + @Override + public int releaseNativeHandle() { + int result = mMojoHandle; + mMojoHandle = CoreImpl.INVALID_HANDLE; + return result; + } + + /** + * Getter for the native scoped handle. + * + * @return the native scoped handle. + */ + int getMojoHandle() { + return mMojoHandle; + } + + /** + * invalidate the handle. The caller must ensures that the handle does not leak. + */ + void invalidateHandle() { + mMojoHandle = CoreImpl.INVALID_HANDLE; + } + + /** + * Close the handle if it is valid. Necessary because we cannot let handle leak, and we cannot + * ensure that every handle will be manually closed. + * + * @see java.lang.Object#finalize() + */ + @Override + protected final void finalize() throws Throwable { + if (isValid()) { + // This should not happen, as the user of this class should close the handle. Adding a + // warning. + Log.w(TAG, "Handle was not closed."); + // Ignore result at this point. + mCore.closeWithResult(mMojoHandle); + } + super.finalize(); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..b3df0aed5becd92666bc4db63373fe8255acad80 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java @@ -0,0 +1,58 @@ +// Copyright 2014 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * Implementation of {@link MessagePipeHandle}. + */ +class MessagePipeHandleImpl extends HandleBase implements MessagePipeHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + MessagePipeHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + MessagePipeHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.MessagePipeHandle#pass() + */ + @Override + public MessagePipeHandle pass() { + return new MessagePipeHandleImpl(this); + } + + /** + * @see MessagePipeHandle#writeMessage(ByteBuffer, List, WriteFlags) + */ + @Override + public void writeMessage(ByteBuffer bytes, List handles, WriteFlags flags) { + mCore.writeMessage(this, bytes, handles, flags); + } + + /** + * @see MessagePipeHandle#readMessage(ByteBuffer, int, ReadFlags) + */ + @Override + public ResultAnd readMessage( + ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags) { + return mCore.readMessage(this, bytes, maxNumberOfHandles, flags); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..76ef73945f59a43911a95b477f767723b42d7a20 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java @@ -0,0 +1,62 @@ +// Copyright 2014 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.SharedBufferHandle; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link SharedBufferHandle}. + */ +class SharedBufferHandleImpl extends HandleBase implements SharedBufferHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + SharedBufferHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + SharedBufferHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.SharedBufferHandle#pass() + */ + @Override + public SharedBufferHandle pass() { + return new SharedBufferHandleImpl(this); + } + + /** + * @see SharedBufferHandle#duplicate(DuplicateOptions) + */ + @Override + public SharedBufferHandle duplicate(DuplicateOptions options) { + return mCore.duplicate(this, options); + } + + /** + * @see SharedBufferHandle#map(long, long, MapFlags) + */ + @Override + public ByteBuffer map(long offset, long numBytes, MapFlags flags) { + return mCore.map(this, offset, numBytes, flags); + } + + /** + * @see SharedBufferHandle#unmap(ByteBuffer) + */ + @Override + public void unmap(ByteBuffer buffer) { + mCore.unmap(buffer); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..4774ab86e94b7ec2e6ba16b06724c9acbdec28a0 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java @@ -0,0 +1,72 @@ +// Copyright 2014 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.UntypedHandle; + +/** + * Implementation of {@link UntypedHandle}. + */ +class UntypedHandleImpl extends HandleBase implements UntypedHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + UntypedHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + UntypedHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#pass() + */ + @Override + public UntypedHandle pass() { + return new UntypedHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toMessagePipeHandle() + */ + @Override + public MessagePipeHandle toMessagePipeHandle() { + return new MessagePipeHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toDataPipeConsumerHandle() + */ + @Override + public ConsumerHandle toDataPipeConsumerHandle() { + return new DataPipeConsumerHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toDataPipeProducerHandle() + */ + @Override + public ProducerHandle toDataPipeProducerHandle() { + return new DataPipeProducerHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toSharedBufferHandle() + */ + @Override + public SharedBufferHandle toSharedBufferHandle() { + return new SharedBufferHandleImpl(this); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..094ad9026539565a188c3c4f8fad99e1d5558373 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java @@ -0,0 +1,63 @@ +// Copyright 2016 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Watcher; + +@JNINamespace("mojo::android") +class WatcherImpl implements Watcher { + private long mImplPtr = nativeCreateWatcher(); + private Callback mCallback; + + @Override + public int start(Handle handle, Core.HandleSignals signals, Callback callback) { + if (mImplPtr == 0) { + return MojoResult.INVALID_ARGUMENT; + } + if (!(handle instanceof HandleBase)) { + return MojoResult.INVALID_ARGUMENT; + } + int result = + nativeStart(mImplPtr, ((HandleBase) handle).getMojoHandle(), signals.getFlags()); + if (result == MojoResult.OK) mCallback = callback; + return result; + } + + @Override + public void cancel() { + if (mImplPtr == 0) { + return; + } + mCallback = null; + nativeCancel(mImplPtr); + } + + @Override + public void destroy() { + if (mImplPtr == 0) { + return; + } + nativeDelete(mImplPtr); + mImplPtr = 0; + } + + @CalledByNative + private void onHandleReady(int result) { + mCallback.onResult(result); + } + + private native long nativeCreateWatcher(); + + private native int nativeStart(long implPtr, int mojoHandle, int flags); + + private native void nativeCancel(long implPtr); + + private native void nativeDelete(long implPtr); +} diff --git a/mojo/android/system/watcher_impl.cc b/mojo/android/system/watcher_impl.cc new file mode 100644 index 0000000000000000000000000000000000000000..3344447f41c41d262633ca1f621d4d8f3c2c4260 --- /dev/null +++ b/mojo/android/system/watcher_impl.cc @@ -0,0 +1,109 @@ +// Copyright 2016 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. + +#include "mojo/android/system/watcher_impl.h" + +#include +#include + +// Removed unused headers. TODO(hidehiko): Upstream. +// #include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +// #include "base/android/jni_registrar.h" +// #include "base/android/library_loader/library_loader_hooks.h" +#include "base/android/scoped_java_ref.h" +#include "base/bind.h" +#include "jni/WatcherImpl_jni.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/simple_watcher.h" + +namespace mojo { +namespace android { + +using base::android::JavaParamRef; + +namespace { + +class WatcherImpl { + public: + WatcherImpl() : watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) {} + + ~WatcherImpl() = default; + + jint Start(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jint signals) { + java_watcher_.Reset(env, jcaller); + + auto ready_callback = + base::Bind(&WatcherImpl::OnHandleReady, base::Unretained(this)); + + MojoResult result = + watcher_.Watch(mojo::Handle(static_cast(mojo_handle)), + static_cast(signals), ready_callback); + if (result != MOJO_RESULT_OK) + java_watcher_.Reset(); + + return result; + } + + void Cancel() { + java_watcher_.Reset(); + watcher_.Cancel(); + } + + private: + void OnHandleReady(MojoResult result) { + DCHECK(!java_watcher_.is_null()); + + base::android::ScopedJavaGlobalRef java_watcher_preserver; + if (result == MOJO_RESULT_CANCELLED) + java_watcher_preserver = std::move(java_watcher_); + + Java_WatcherImpl_onHandleReady( + base::android::AttachCurrentThread(), + java_watcher_.is_null() ? java_watcher_preserver : java_watcher_, + result); + } + + SimpleWatcher watcher_; + base::android::ScopedJavaGlobalRef java_watcher_; + + DISALLOW_COPY_AND_ASSIGN(WatcherImpl); +}; + +} // namespace + +static jlong CreateWatcher(JNIEnv* env, const JavaParamRef& jcaller) { + return reinterpret_cast(new WatcherImpl); +} + +static jint Start(JNIEnv* env, + const JavaParamRef& jcaller, + jlong watcher_ptr, + jint mojo_handle, + jint signals) { + auto* watcher = reinterpret_cast(watcher_ptr); + return watcher->Start(env, jcaller, mojo_handle, signals); +} + +static void Cancel(JNIEnv* env, + const JavaParamRef& jcaller, + jlong watcher_ptr) { + reinterpret_cast(watcher_ptr)->Cancel(); +} + +static void Delete(JNIEnv* env, + const JavaParamRef& jcaller, + jlong watcher_ptr) { + delete reinterpret_cast(watcher_ptr); +} + +bool RegisterWatcherImpl(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/system/watcher_impl.h b/mojo/android/system/watcher_impl.h new file mode 100644 index 0000000000000000000000000000000000000000..784f007e25f4dbee00496ee9079ab4365f876da4 --- /dev/null +++ b/mojo/android/system/watcher_impl.h @@ -0,0 +1,20 @@ +// Copyright 2016 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 MOJO_ANDROID_SYSTEM_WATCHER_IMPL_H_ +#define MOJO_ANDROID_SYSTEM_WATCHER_IMPL_H_ + +#include + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterWatcherImpl(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_ANDROID_SYSTEM_WATCHER_IMPL_H_ diff --git a/mojo/common/BUILD.gn b/mojo/common/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..9e74e582d3eec5ecbff2b76770d202fd5a4b64b7 --- /dev/null +++ b/mojo/common/BUILD.gn @@ -0,0 +1,93 @@ +# Copyright 2014 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("//mojo/public/tools/bindings/mojom.gni") +import("//testing/test.gni") + +group("common") { + public_deps = [ + ":common_base", + ":common_custom_types", + ] +} + +mojom("common_custom_types") { + sources = [ + "file.mojom", + "file_path.mojom", + "string16.mojom", + "text_direction.mojom", + "time.mojom", + "unguessable_token.mojom", + "values.mojom", + "version.mojom", + ] +} + +component("common_base") { + output_name = "mojo_common_lib" + + sources = [ + "data_pipe_drainer.cc", + "data_pipe_drainer.h", + "data_pipe_utils.cc", + "data_pipe_utils.h", + ] + + defines = [ "MOJO_COMMON_IMPLEMENTATION" ] + + public_deps = [ + "//base", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/system", + ] +} + +mojom("test_common_custom_types") { + sources = [ + "test_common_custom_types.mojom", + "traits_test_service.mojom", + ] + public_deps = [ + ":common_custom_types", + ] +} + +test("mojo_common_unittests") { + deps = [ + ":common", + ":common_custom_types", + ":struct_traits", + ":test_common_custom_types", + "//base", + "//base:message_loop_tests", + "//base/test:test_support", + "//mojo/edk/test:run_all_unittests", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/test_support:test_utils", + "//testing/gtest", + "//url", + ] + + sources = [ + "common_custom_types_unittest.cc", + "struct_traits_unittest.cc", + ] +} + +source_set("struct_traits") { + sources = [ + "common_custom_types_struct_traits.cc", + "common_custom_types_struct_traits.h", + ] + deps = [ + ":common_custom_types_shared_cpp_sources", + "//base:base", + "//mojo/public/cpp/system", + ] + public_deps = [ + "//base:i18n", + ] +} diff --git a/mojo/common/DEPS b/mojo/common/DEPS new file mode 100644 index 0000000000000000000000000000000000000000..e8ac42887d1ccaed04ed289c8aff3b70f24a04b4 --- /dev/null +++ b/mojo/common/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + # common must not depend on embedder. + "-mojo", + "+mojo/common", + "+mojo/public", +] diff --git a/mojo/common/common_custom_types_struct_traits.cc b/mojo/common/common_custom_types_struct_traits.cc new file mode 100644 index 0000000000000000000000000000000000000000..62895048ad2824ba538fbf78211e9baa93a35a4b --- /dev/null +++ b/mojo/common/common_custom_types_struct_traits.cc @@ -0,0 +1,108 @@ +// Copyright 2016 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. + +#include "mojo/common/common_custom_types_struct_traits.h" + +#include "mojo/public/cpp/system/platform_handle.h" + +namespace mojo { + +// static +bool StructTraits::Read( + common::mojom::String16DataView data, + base::string16* out) { + ArrayDataView view; + data.GetDataDataView(&view); + out->assign(reinterpret_cast(view.data()), view.size()); + return true; +} + +// static +const std::vector& +StructTraits::components( + const base::Version& version) { + return version.components(); +} + +// static +bool StructTraits::Read( + common::mojom::VersionDataView data, + base::Version* out) { + std::vector components; + if (!data.ReadComponents(&components)) + return false; + + *out = base::Version(base::Version(std::move(components))); + return out->IsValid(); +} + +// static +bool StructTraits< + common::mojom::UnguessableTokenDataView, + base::UnguessableToken>::Read(common::mojom::UnguessableTokenDataView data, + base::UnguessableToken* out) { + uint64_t high = data.high(); + uint64_t low = data.low(); + + // Receiving a zeroed UnguessableToken is a security issue. + if (high == 0 && low == 0) + return false; + + *out = base::UnguessableToken::Deserialize(high, low); + return true; +} + +mojo::ScopedHandle StructTraits::fd( + base::File& file) { + DCHECK(file.IsValid()); + return mojo::WrapPlatformFile(file.TakePlatformFile()); +} + +bool StructTraits::Read( + common::mojom::FileDataView data, + base::File* file) { + base::PlatformFile platform_handle = base::kInvalidPlatformFile; + if (mojo::UnwrapPlatformFile(data.TakeFd(), &platform_handle) != + MOJO_RESULT_OK) { + return false; + } + *file = base::File(platform_handle); + return true; +} + +// static +common::mojom::TextDirection +EnumTraits::ToMojom( + base::i18n::TextDirection text_direction) { + switch (text_direction) { + case base::i18n::UNKNOWN_DIRECTION: + return common::mojom::TextDirection::UNKNOWN_DIRECTION; + case base::i18n::RIGHT_TO_LEFT: + return common::mojom::TextDirection::RIGHT_TO_LEFT; + case base::i18n::LEFT_TO_RIGHT: + return common::mojom::TextDirection::LEFT_TO_RIGHT; + } + NOTREACHED(); + return common::mojom::TextDirection::UNKNOWN_DIRECTION; +} + +// static +bool EnumTraits:: + FromMojom(common::mojom::TextDirection input, + base::i18n::TextDirection* out) { + switch (input) { + case common::mojom::TextDirection::UNKNOWN_DIRECTION: + *out = base::i18n::UNKNOWN_DIRECTION; + return true; + case common::mojom::TextDirection::RIGHT_TO_LEFT: + *out = base::i18n::RIGHT_TO_LEFT; + return true; + case common::mojom::TextDirection::LEFT_TO_RIGHT: + *out = base::i18n::LEFT_TO_RIGHT; + return true; + } + return false; +} + +} // namespace mojo diff --git a/mojo/common/common_custom_types_struct_traits.h b/mojo/common/common_custom_types_struct_traits.h new file mode 100644 index 0000000000000000000000000000000000000000..85815ffd625082c1707f33a110441f3f8aa5797d --- /dev/null +++ b/mojo/common/common_custom_types_struct_traits.h @@ -0,0 +1,85 @@ +// Copyright 2016 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 MOJO_COMMON_COMMON_CUSTOM_TYPES_STRUCT_TRAITS_H_ +#define MOJO_COMMON_COMMON_CUSTOM_TYPES_STRUCT_TRAITS_H_ + +#include "base/files/file.h" +#include "base/i18n/rtl.h" +#include "base/strings/utf_string_conversions.h" +#include "base/unguessable_token.h" +#include "base/version.h" +#include "mojo/common/file.mojom-shared.h" +#include "mojo/common/mojo_common_export.h" +#include "mojo/common/string16.mojom-shared.h" +#include "mojo/common/text_direction.mojom-shared.h" +#include "mojo/common/time.mojom-shared.h" +#include "mojo/common/unguessable_token.mojom-shared.h" +#include "mojo/common/version.mojom-shared.h" + +namespace mojo { + +template <> +struct StructTraits { + static ConstCArray data(const base::string16& str) { + return ConstCArray(str.size(), + reinterpret_cast(str.data())); + } + + static bool Read(common::mojom::String16DataView data, base::string16* out); +}; + +template <> +struct StructTraits { + static bool IsNull(const base::Version& version) { + return !version.IsValid(); + } + static void SetToNull(base::Version* out) { + *out = base::Version(std::string()); + } + static const std::vector& components(const base::Version& version); + static bool Read(common::mojom::VersionDataView data, base::Version* out); +}; + +// If base::UnguessableToken is no longer 128 bits, the logic below and the +// mojom::UnguessableToken type should be updated. +static_assert(sizeof(base::UnguessableToken) == 2 * sizeof(uint64_t), + "base::UnguessableToken should be of size 2 * sizeof(uint64_t)."); + +template <> +struct StructTraits { + static uint64_t high(const base::UnguessableToken& token) { + return token.GetHighForSerialization(); + } + + static uint64_t low(const base::UnguessableToken& token) { + return token.GetLowForSerialization(); + } + + static bool Read(common::mojom::UnguessableTokenDataView data, + base::UnguessableToken* out); +}; + +template <> +struct StructTraits { + static bool IsNull(const base::File& file) { return !file.IsValid(); } + + static void SetToNull(base::File* file) { *file = base::File(); } + + static mojo::ScopedHandle fd(base::File& file); + static bool Read(common::mojom::FileDataView data, base::File* file); +}; + +template <> +struct EnumTraits { + static common::mojom::TextDirection ToMojom( + base::i18n::TextDirection text_direction); + static bool FromMojom(common::mojom::TextDirection input, + base::i18n::TextDirection* out); +}; + +} // namespace mojo + +#endif // MOJO_COMMON_COMMON_CUSTOM_TYPES_STRUCT_TRAITS_H_ diff --git a/mojo/common/common_custom_types_unittest.cc b/mojo/common/common_custom_types_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..e3571d9d86cf65e54718868e089bf976101d4929 --- /dev/null +++ b/mojo/common/common_custom_types_unittest.cc @@ -0,0 +1,433 @@ +// Copyright 2016 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. + +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/numerics/safe_math.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "mojo/common/test_common_custom_types.mojom.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace common { +namespace test { +namespace { + +template +struct BounceTestTraits { + static void ExpectEquality(const T& a, const T& b) { + EXPECT_EQ(a, b); + } +}; + +template +struct PassTraits { + using Type = const T&; +}; + +template <> +struct PassTraits { + using Type = base::Time; +}; + +template <> +struct PassTraits { + using Type = base::TimeDelta; +}; + +template <> +struct PassTraits { + using Type = base::TimeTicks; +}; + +template +void DoExpectResponse(T* expected_value, + const base::Closure& closure, + typename PassTraits::Type value) { + BounceTestTraits::ExpectEquality(*expected_value, value); + closure.Run(); +} + +template +base::Callback::Type)> ExpectResponse( + T* expected_value, + const base::Closure& closure) { + return base::Bind(&DoExpectResponse, expected_value, closure); +} + +class TestFilePathImpl : public TestFilePath { + public: + explicit TestFilePathImpl(TestFilePathRequest request) + : binding_(this, std::move(request)) {} + + // TestFilePath implementation: + void BounceFilePath(const base::FilePath& in, + const BounceFilePathCallback& callback) override { + callback.Run(in); + } + + private: + mojo::Binding binding_; +}; + +class TestUnguessableTokenImpl : public TestUnguessableToken { + public: + explicit TestUnguessableTokenImpl(TestUnguessableTokenRequest request) + : binding_(this, std::move(request)) {} + + // TestUnguessableToken implementation: + void BounceNonce(const base::UnguessableToken& in, + const BounceNonceCallback& callback) override { + callback.Run(in); + } + + private: + mojo::Binding binding_; +}; + +class TestTimeImpl : public TestTime { + public: + explicit TestTimeImpl(TestTimeRequest request) + : binding_(this, std::move(request)) {} + + // TestTime implementation: + void BounceTime(base::Time in, const BounceTimeCallback& callback) override { + callback.Run(in); + } + + void BounceTimeDelta(base::TimeDelta in, + const BounceTimeDeltaCallback& callback) override { + callback.Run(in); + } + + void BounceTimeTicks(base::TimeTicks in, + const BounceTimeTicksCallback& callback) override { + callback.Run(in); + } + + private: + mojo::Binding binding_; +}; + +class TestValueImpl : public TestValue { + public: + explicit TestValueImpl(TestValueRequest request) + : binding_(this, std::move(request)) {} + + // TestValue implementation: + void BounceDictionaryValue( + std::unique_ptr in, + const BounceDictionaryValueCallback& callback) override { + callback.Run(std::move(in)); + } + + void BounceListValue(std::unique_ptr in, + const BounceListValueCallback& callback) override { + callback.Run(std::move(in)); + } + + void BounceValue(std::unique_ptr in, + const BounceValueCallback& callback) override { + callback.Run(std::move(in)); + } + + private: + mojo::Binding binding_; +}; + +class TestString16Impl : public TestString16 { + public: + explicit TestString16Impl(TestString16Request request) + : binding_(this, std::move(request)) {} + + // TestString16 implementation: + void BounceString16(const base::string16& in, + const BounceString16Callback& callback) override { + callback.Run(in); + } + + private: + mojo::Binding binding_; +}; + +class TestFileImpl : public TestFile { + public: + explicit TestFileImpl(TestFileRequest request) + : binding_(this, std::move(request)) {} + + // TestFile implementation: + void BounceFile(base::File in, const BounceFileCallback& callback) override { + callback.Run(std::move(in)); + } + + private: + mojo::Binding binding_; +}; + +class TestTextDirectionImpl : public TestTextDirection { + public: + explicit TestTextDirectionImpl(TestTextDirectionRequest request) + : binding_(this, std::move(request)) {} + + // TestTextDirection: + void BounceTextDirection( + base::i18n::TextDirection in, + const BounceTextDirectionCallback& callback) override { + callback.Run(in); + } + + private: + mojo::Binding binding_; +}; + +class CommonCustomTypesTest : public testing::Test { + protected: + CommonCustomTypesTest() {} + ~CommonCustomTypesTest() override {} + + private: + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(CommonCustomTypesTest); +}; + +} // namespace + +TEST_F(CommonCustomTypesTest, FilePath) { + base::RunLoop run_loop; + + TestFilePathPtr ptr; + TestFilePathImpl impl(MakeRequest(&ptr)); + + base::FilePath dir(FILE_PATH_LITERAL("hello")); + base::FilePath file = dir.Append(FILE_PATH_LITERAL("world")); + + ptr->BounceFilePath(file, ExpectResponse(&file, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, UnguessableToken) { + base::RunLoop run_loop; + + TestUnguessableTokenPtr ptr; + TestUnguessableTokenImpl impl(MakeRequest(&ptr)); + + base::UnguessableToken token = base::UnguessableToken::Create(); + + ptr->BounceNonce(token, ExpectResponse(&token, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, Time) { + base::RunLoop run_loop; + + TestTimePtr ptr; + TestTimeImpl impl(MakeRequest(&ptr)); + + base::Time t = base::Time::Now(); + + ptr->BounceTime(t, ExpectResponse(&t, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, TimeDelta) { + base::RunLoop run_loop; + + TestTimePtr ptr; + TestTimeImpl impl(MakeRequest(&ptr)); + + base::TimeDelta t = base::TimeDelta::FromDays(123); + + ptr->BounceTimeDelta(t, ExpectResponse(&t, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, TimeTicks) { + base::RunLoop run_loop; + + TestTimePtr ptr; + TestTimeImpl impl(MakeRequest(&ptr)); + + base::TimeTicks t = base::TimeTicks::Now(); + + ptr->BounceTimeTicks(t, ExpectResponse(&t, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, Value) { + TestValuePtr ptr; + TestValueImpl impl(MakeRequest(&ptr)); + + std::unique_ptr output; + + ASSERT_TRUE(ptr->BounceValue(nullptr, &output)); + EXPECT_FALSE(output); + + std::unique_ptr input = base::Value::CreateNullValue(); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + input = base::MakeUnique(123); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + input = base::MakeUnique(1.23); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + input = base::MakeUnique(false); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + input = base::MakeUnique("test string"); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + input = base::BinaryValue::CreateWithCopiedBuffer("mojo", 4); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + auto dict = base::MakeUnique(); + dict->SetBoolean("bool", false); + dict->SetInteger("int", 2); + dict->SetString("string", "some string"); + dict->SetBoolean("nested.bool", true); + dict->SetInteger("nested.int", 9); + dict->Set("some_binary", + base::BinaryValue::CreateWithCopiedBuffer("mojo", 4)); + dict->Set("null_value", base::Value::CreateNullValue()); + dict->SetIntegerWithoutPathExpansion("non_nested.int", 10); + { + std::unique_ptr dict_list(new base::ListValue()); + dict_list->AppendString("string"); + dict_list->AppendBoolean(true); + dict->Set("list", std::move(dict_list)); + } + + std::unique_ptr dict_output; + ASSERT_TRUE(ptr->BounceDictionaryValue(dict->CreateDeepCopy(), &dict_output)); + EXPECT_TRUE(base::Value::Equals(dict.get(), dict_output.get())); + + input = std::move(dict); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + auto list = base::MakeUnique(); + list->AppendString("string"); + list->AppendDouble(42.1); + list->AppendBoolean(true); + list->Append(base::BinaryValue::CreateWithCopiedBuffer("mojo", 4)); + list->Append(base::Value::CreateNullValue()); + { + std::unique_ptr list_dict( + new base::DictionaryValue()); + list_dict->SetString("string", "str"); + list->Append(std::move(list_dict)); + } + std::unique_ptr list_output; + ASSERT_TRUE(ptr->BounceListValue(list->CreateDeepCopy(), &list_output)); + EXPECT_TRUE(base::Value::Equals(list.get(), list_output.get())); + + input = std::move(list); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + ASSERT_TRUE(base::Value::Equals(input.get(), output.get())); +} + +TEST_F(CommonCustomTypesTest, String16) { + base::RunLoop run_loop; + + TestString16Ptr ptr; + TestString16Impl impl(MakeRequest(&ptr)); + + base::string16 str16 = base::ASCIIToUTF16("hello world"); + + ptr->BounceString16(str16, ExpectResponse(&str16, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, EmptyString16) { + base::RunLoop run_loop; + + TestString16Ptr ptr; + TestString16Impl impl(MakeRequest(&ptr)); + + base::string16 str16; + + ptr->BounceString16(str16, ExpectResponse(&str16, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, File) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + TestFilePtr ptr; + TestFileImpl impl(MakeRequest(&ptr)); + + base::File file( + temp_dir.GetPath().AppendASCII("test_file.txt"), + base::File::FLAG_CREATE | base::File::FLAG_WRITE | base::File::FLAG_READ); + const base::StringPiece test_content = + "A test string to be stored in a test file"; + file.WriteAtCurrentPos( + test_content.data(), + base::CheckedNumeric(test_content.size()).ValueOrDie()); + + base::File file_out; + ASSERT_TRUE(ptr->BounceFile(std::move(file), &file_out)); + std::vector content(test_content.size()); + ASSERT_TRUE(file_out.IsValid()); + ASSERT_EQ(static_cast(test_content.size()), + file_out.Read( + 0, content.data(), + base::CheckedNumeric(test_content.size()).ValueOrDie())); + EXPECT_EQ(test_content, + base::StringPiece(content.data(), test_content.size())); +} + +TEST_F(CommonCustomTypesTest, InvalidFile) { + TestFilePtr ptr; + TestFileImpl impl(MakeRequest(&ptr)); + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + // Test that |file_out| is set to an invalid file. + base::File file_out( + temp_dir.GetPath().AppendASCII("test_file.txt"), + base::File::FLAG_CREATE | base::File::FLAG_WRITE | base::File::FLAG_READ); + + ASSERT_TRUE(ptr->BounceFile(base::File(), &file_out)); + EXPECT_FALSE(file_out.IsValid()); +} + +TEST_F(CommonCustomTypesTest, TextDirection) { + base::i18n::TextDirection kTestDirections[] = {base::i18n::LEFT_TO_RIGHT, + base::i18n::RIGHT_TO_LEFT, + base::i18n::UNKNOWN_DIRECTION}; + + TestTextDirectionPtr ptr; + TestTextDirectionImpl impl(MakeRequest(&ptr)); + + for (size_t i = 0; i < arraysize(kTestDirections); i++) { + base::i18n::TextDirection direction_out; + ASSERT_TRUE(ptr->BounceTextDirection(kTestDirections[i], &direction_out)); + EXPECT_EQ(kTestDirections[i], direction_out); + } +} + +} // namespace test +} // namespace common +} // namespace mojo diff --git a/mojo/common/data_pipe_drainer.cc b/mojo/common/data_pipe_drainer.cc new file mode 100644 index 0000000000000000000000000000000000000000..e705c8d387f28fc84b823e26fcef6997b5ea4412 --- /dev/null +++ b/mojo/common/data_pipe_drainer.cc @@ -0,0 +1,50 @@ +// Copyright 2014 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. + +#include "mojo/common/data_pipe_drainer.h" + +#include + +#include + +#include "base/bind.h" + +namespace mojo { +namespace common { + +DataPipeDrainer::DataPipeDrainer(Client* client, + mojo::ScopedDataPipeConsumerHandle source) + : client_(client), + source_(std::move(source)), + handle_watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC), + weak_factory_(this) { + DCHECK(client_); + handle_watcher_.Watch( + source_.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind(&DataPipeDrainer::WaitComplete, weak_factory_.GetWeakPtr())); +} + +DataPipeDrainer::~DataPipeDrainer() {} + +void DataPipeDrainer::ReadData() { + const void* buffer = nullptr; + uint32_t num_bytes = 0; + MojoResult rv = BeginReadDataRaw(source_.get(), &buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); + if (rv == MOJO_RESULT_OK) { + client_->OnDataAvailable(buffer, num_bytes); + EndReadDataRaw(source_.get(), num_bytes); + } else if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + client_->OnDataComplete(); + } else if (rv != MOJO_RESULT_SHOULD_WAIT) { + DCHECK(false) << "Unhandled MojoResult: " << rv; + } +} + +void DataPipeDrainer::WaitComplete(MojoResult result) { + ReadData(); +} + +} // namespace common +} // namespace mojo diff --git a/mojo/common/data_pipe_drainer.h b/mojo/common/data_pipe_drainer.h new file mode 100644 index 0000000000000000000000000000000000000000..5cff8203e09f8091dae020e9ff2796d7436ff814 --- /dev/null +++ b/mojo/common/data_pipe_drainer.h @@ -0,0 +1,49 @@ +// Copyright 2014 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 MOJO_COMMON_DATA_PIPE_DRAINER_H_ +#define MOJO_COMMON_DATA_PIPE_DRAINER_H_ + +#include + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "mojo/common/mojo_common_export.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/simple_watcher.h" + +namespace mojo { +namespace common { + +class MOJO_COMMON_EXPORT DataPipeDrainer { + public: + class Client { + public: + virtual void OnDataAvailable(const void* data, size_t num_bytes) = 0; + virtual void OnDataComplete() = 0; + + protected: + virtual ~Client() {} + }; + + DataPipeDrainer(Client*, mojo::ScopedDataPipeConsumerHandle source); + ~DataPipeDrainer(); + + private: + void ReadData(); + void WaitComplete(MojoResult result); + + Client* client_; + mojo::ScopedDataPipeConsumerHandle source_; + mojo::SimpleWatcher handle_watcher_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DataPipeDrainer); +}; + +} // namespace common +} // namespace mojo + +#endif // MOJO_COMMON_DATA_PIPE_DRAINER_H_ diff --git a/mojo/common/data_pipe_utils.cc b/mojo/common/data_pipe_utils.cc new file mode 100644 index 0000000000000000000000000000000000000000..9b069b80c5832c44e2ac18acfb969539f53e2bcf --- /dev/null +++ b/mojo/common/data_pipe_utils.cc @@ -0,0 +1,96 @@ +// Copyright 2014 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. + +#include "mojo/common/data_pipe_utils.h" + +#include + +#include "base/bind.h" +#include "mojo/public/cpp/system/wait.h" + +namespace mojo { +namespace common { +namespace { + +bool BlockingCopyHelper(ScopedDataPipeConsumerHandle source, + const base::Callback& write_bytes) { + for (;;) { + const void* buffer; + uint32_t num_bytes; + MojoResult result = BeginReadDataRaw( + source.get(), &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + size_t bytes_written = write_bytes.Run(buffer, num_bytes); + result = EndReadDataRaw(source.get(), num_bytes); + if (bytes_written < num_bytes || result != MOJO_RESULT_OK) + return false; + } else if (result == MOJO_RESULT_SHOULD_WAIT) { + result = Wait(source.get(), MOJO_HANDLE_SIGNAL_READABLE); + if (result != MOJO_RESULT_OK) { + // If the producer handle was closed, then treat as EOF. + return result == MOJO_RESULT_FAILED_PRECONDITION; + } + } else if (result == MOJO_RESULT_FAILED_PRECONDITION) { + // If the producer handle was closed, then treat as EOF. + return true; + } else { + // Some other error occurred. + break; + } + } + + return false; +} + +size_t CopyToStringHelper( + std::string* result, const void* buffer, uint32_t num_bytes) { + result->append(static_cast(buffer), num_bytes); + return num_bytes; +} + +} // namespace + +// TODO(hansmuller): Add a max_size parameter. +bool BlockingCopyToString(ScopedDataPipeConsumerHandle source, + std::string* result) { + CHECK(result); + result->clear(); + return BlockingCopyHelper(std::move(source), + base::Bind(&CopyToStringHelper, result)); +} + +bool MOJO_COMMON_EXPORT BlockingCopyFromString( + const std::string& source, + const ScopedDataPipeProducerHandle& destination) { + auto it = source.begin(); + for (;;) { + void* buffer = nullptr; + uint32_t buffer_num_bytes = 0; + MojoResult result = + BeginWriteDataRaw(destination.get(), &buffer, &buffer_num_bytes, + MOJO_WRITE_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + char* char_buffer = static_cast(buffer); + uint32_t byte_index = 0; + while (it != source.end() && byte_index < buffer_num_bytes) { + char_buffer[byte_index++] = *it++; + } + EndWriteDataRaw(destination.get(), byte_index); + if (it == source.end()) + return true; + } else if (result == MOJO_RESULT_SHOULD_WAIT) { + result = Wait(destination.get(), MOJO_HANDLE_SIGNAL_WRITABLE); + if (result != MOJO_RESULT_OK) { + // If the consumer handle was closed, then treat as EOF. + return result == MOJO_RESULT_FAILED_PRECONDITION; + } + } else { + // If the consumer handle was closed, then treat as EOF. + return result == MOJO_RESULT_FAILED_PRECONDITION; + } + } +} + +} // namespace common +} // namespace mojo diff --git a/mojo/common/data_pipe_utils.h b/mojo/common/data_pipe_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..a3f7c093e47550e2f0bbdcbc0237e69ded523def --- /dev/null +++ b/mojo/common/data_pipe_utils.h @@ -0,0 +1,32 @@ +// Copyright 2014 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 MOJO_COMMON_DATA_PIPE_UTILS_H_ +#define MOJO_COMMON_DATA_PIPE_UTILS_H_ + +#include + +#include + +#include "mojo/common/mojo_common_export.h" +#include "mojo/public/cpp/system/data_pipe.h" + +namespace mojo { +namespace common { + +// Copies the data from |source| into |contents| and returns true on success and +// false on error. In case of I/O error, |contents| holds the data that could +// be read from source before the error occurred. +bool MOJO_COMMON_EXPORT BlockingCopyToString( + ScopedDataPipeConsumerHandle source, + std::string* contents); + +bool MOJO_COMMON_EXPORT BlockingCopyFromString( + const std::string& source, + const ScopedDataPipeProducerHandle& destination); + +} // namespace common +} // namespace mojo + +#endif // MOJO_COMMON_DATA_PIPE_UTILS_H_ diff --git a/mojo/common/file.mojom b/mojo/common/file.mojom new file mode 100644 index 0000000000000000000000000000000000000000..fe224734d94b3488a2354a2156a87af94648f1c5 --- /dev/null +++ b/mojo/common/file.mojom @@ -0,0 +1,10 @@ +// Copyright 2016 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. + +module mojo.common.mojom; + +// Corresponds to |base::File| in base/files/file.h +struct File { + handle fd; +}; diff --git a/mojo/common/file.typemap b/mojo/common/file.typemap new file mode 100644 index 0000000000000000000000000000000000000000..26d494139e875e25f63d3bb56fa94dee87add474 --- /dev/null +++ b/mojo/common/file.typemap @@ -0,0 +1,13 @@ +# Copyright 2016 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. + +mojom = "//mojo/common/file.mojom" +public_headers = [ "//base/files/file.h" ] +traits_headers = [ "//mojo/common/common_custom_types_struct_traits.h" ] +public_deps = [ + "//mojo/common:struct_traits", +] + +type_mappings = + [ "mojo.common.mojom.File=base::File[move_only,nullable_is_same_type]" ] diff --git a/mojo/common/file_path.mojom b/mojo/common/file_path.mojom new file mode 100644 index 0000000000000000000000000000000000000000..10ebe058be83f9990f215b424b80182eba516945 --- /dev/null +++ b/mojo/common/file_path.mojom @@ -0,0 +1,8 @@ +// Copyright 2016 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. + +module mojo.common.mojom; + +[Native] +struct FilePath; diff --git a/mojo/common/file_path.typemap b/mojo/common/file_path.typemap new file mode 100644 index 0000000000000000000000000000000000000000..66d8c54da8aae87dd8f4af8cc6284a27326e07e3 --- /dev/null +++ b/mojo/common/file_path.typemap @@ -0,0 +1,12 @@ +# Copyright 2016 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. + +mojom = "//mojo/common/file_path.mojom" +public_headers = [ "//base/files/file_path.h" ] +traits_headers = [ "//ipc/ipc_message_utils.h" ] +public_deps = [ + "//ipc", +] + +type_mappings = [ "mojo.common.mojom.FilePath=base::FilePath" ] diff --git a/mojo/common/mojo_common_export.h b/mojo/common/mojo_common_export.h new file mode 100644 index 0000000000000000000000000000000000000000..48d21d0d3d2ee4b15a2e2b3b3d0c700b77a39697 --- /dev/null +++ b/mojo/common/mojo_common_export.h @@ -0,0 +1,32 @@ +// Copyright 2013 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 MOJO_COMMON_MOJO_COMMON_EXPORT_H_ +#define MOJO_COMMON_MOJO_COMMON_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_COMMON_IMPLEMENTATION) +#define MOJO_COMMON_EXPORT __declspec(dllexport) +#else +#define MOJO_COMMON_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_COMMON_IMPLEMENTATION) +#define MOJO_COMMON_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_COMMON_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) +#define MOJO_COMMON_EXPORT +#endif + +#endif // MOJO_COMMON_MOJO_COMMON_EXPORT_H_ diff --git a/mojo/common/string16.mojom b/mojo/common/string16.mojom new file mode 100644 index 0000000000000000000000000000000000000000..173c8670cd151ea2ee45cd66027a3306b6012365 --- /dev/null +++ b/mojo/common/string16.mojom @@ -0,0 +1,12 @@ +// Copyright 2016 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. + +module mojo.common.mojom; + +// Corresponds to |base::string16| in base/strings/string16.h +// Corresponds to |WTF::String| in +// third_party/WebKit/Source/wtf/text/WTFString.h. +struct String16 { + array data; +}; diff --git a/mojo/common/string16.typemap b/mojo/common/string16.typemap new file mode 100644 index 0000000000000000000000000000000000000000..223de29c0daeac8f7cb1515a538b21cdcd31afbf --- /dev/null +++ b/mojo/common/string16.typemap @@ -0,0 +1,12 @@ +# Copyright 2016 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. + +mojom = "//mojo/common/string16.mojom" +public_headers = [ "//base/strings/string16.h" ] +traits_headers = [ "//mojo/common/common_custom_types_struct_traits.h" ] +public_deps = [ + "//mojo/common:struct_traits", +] + +type_mappings = [ "mojo.common.mojom.String16=base::string16" ] diff --git a/mojo/common/struct_traits_unittest.cc b/mojo/common/struct_traits_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..5ac4bc9c800066a318d5aed5dec395ce288145aa --- /dev/null +++ b/mojo/common/struct_traits_unittest.cc @@ -0,0 +1,57 @@ +// Copyright 2016 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. + +#include "base/message_loop/message_loop.h" +#include "mojo/common/traits_test_service.mojom.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace common { +namespace { + +class StructTraitsTest : public testing::Test, public mojom::TraitsTestService { + public: + StructTraitsTest() {} + + protected: + mojom::TraitsTestServicePtr GetTraitsTestProxy() { + return traits_test_bindings_.CreateInterfacePtrAndBind(this); + } + + private: + // TraitsTestService: + void EchoVersion(const base::Optional& m, + const EchoVersionCallback& callback) override { + callback.Run(m); + } + + base::MessageLoop loop_; + mojo::BindingSet traits_test_bindings_; + + DISALLOW_COPY_AND_ASSIGN(StructTraitsTest); +}; + +TEST_F(StructTraitsTest, Version) { + const std::string& version_str = "1.2.3.4"; + base::Version input(version_str); + mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy(); + base::Optional output; + proxy->EchoVersion(input, &output); + EXPECT_TRUE(output.has_value()); + EXPECT_EQ(version_str, output->GetString()); +} + +TEST_F(StructTraitsTest, InvalidVersion) { + const std::string invalid_version_str; + base::Version input(invalid_version_str); + mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy(); + base::Optional output; + proxy->EchoVersion(input, &output); + EXPECT_FALSE(output.has_value()); +} + +} // namespace +} // namespace common +} // namespace mojo diff --git a/mojo/common/test_common_custom_types.mojom b/mojo/common/test_common_custom_types.mojom new file mode 100644 index 0000000000000000000000000000000000000000..0f13680f6827c771b65a15129d944132d7166856 --- /dev/null +++ b/mojo/common/test_common_custom_types.mojom @@ -0,0 +1,61 @@ +// Copyright 2016 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. + +module mojo.common.test; + +import "mojo/common/file.mojom"; +import "mojo/common/file_path.mojom"; +import "mojo/common/string16.mojom"; +import "mojo/common/text_direction.mojom"; +import "mojo/common/time.mojom"; +import "mojo/common/unguessable_token.mojom"; +import "mojo/common/values.mojom"; + +interface TestFilePath { + BounceFilePath(mojo.common.mojom.FilePath in) + => (mojo.common.mojom.FilePath out); +}; + +interface TestUnguessableToken { + BounceNonce(mojo.common.mojom.UnguessableToken in) + => (mojo.common.mojom.UnguessableToken out); +}; + +interface TestTime { + BounceTime(mojo.common.mojom.Time time) => (mojo.common.mojom.Time time); + BounceTimeDelta(mojo.common.mojom.TimeDelta time_delta) + => (mojo.common.mojom.TimeDelta time_delta); + BounceTimeTicks(mojo.common.mojom.TimeTicks time_ticks) + => (mojo.common.mojom.TimeTicks time_ticks); +}; + +interface TestValue { + [Sync] + BounceDictionaryValue(mojo.common.mojom.DictionaryValue in) + => (mojo.common.mojom.DictionaryValue out); + [Sync] + BounceListValue(mojo.common.mojom.ListValue in) + => (mojo.common.mojom.ListValue out); + [Sync] + BounceValue(mojo.common.mojom.Value? in) + => (mojo.common.mojom.Value? out); +}; + +interface TestString16 { + [Sync] + BounceString16(mojo.common.mojom.String16 in) + => (mojo.common.mojom.String16 out); +}; + +interface TestFile { + [Sync] + BounceFile(mojo.common.mojom.File? in) + => (mojo.common.mojom.File? out); +}; + +interface TestTextDirection { + [Sync] + BounceTextDirection(mojo.common.mojom.TextDirection in) + => (mojo.common.mojom.TextDirection out); +}; diff --git a/mojo/common/text_direction.mojom b/mojo/common/text_direction.mojom new file mode 100644 index 0000000000000000000000000000000000000000..7d651245eda5ba6359ab3418b7fefe0085ebfb7b --- /dev/null +++ b/mojo/common/text_direction.mojom @@ -0,0 +1,12 @@ +// 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. + +module mojo.common.mojom; + +// Corresponds to |base::i18n::TextDirection| in base/i18n/rtl.h +enum TextDirection { + UNKNOWN_DIRECTION, + RIGHT_TO_LEFT, + LEFT_TO_RIGHT +}; diff --git a/mojo/common/text_direction.typemap b/mojo/common/text_direction.typemap new file mode 100644 index 0000000000000000000000000000000000000000..1f5be8edbafb57916c2f6c74dc8b8aacbcbb3f96 --- /dev/null +++ b/mojo/common/text_direction.typemap @@ -0,0 +1,12 @@ +# 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. + +mojom = "//mojo/common/text_direction.mojom" +public_headers = [ "//base/i18n/rtl.h" ] +traits_headers = [ "//mojo/common/common_custom_types_struct_traits.h" ] +public_deps = [ + "//base:i18n", + "//mojo/common:struct_traits", +] +type_mappings = [ "mojo.common.mojom.TextDirection=base::i18n::TextDirection" ] diff --git a/mojo/common/time.mojom b/mojo/common/time.mojom new file mode 100644 index 0000000000000000000000000000000000000000..b403bcac3a698729d2afc4a5afbec4d74f7fd78b --- /dev/null +++ b/mojo/common/time.mojom @@ -0,0 +1,21 @@ +// Copyright 2016 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. + +module mojo.common.mojom; + +struct Time { + // The internal value is expressed in terms of microseconds since a fixed but + // intentionally unspecified epoch. + int64 internal_value; +}; + +struct TimeDelta { + int64 microseconds; +}; + +struct TimeTicks { + // The internal value is expressed in terms of microseconds since a fixed but + // intentionally unspecified epoch. + int64 internal_value; +}; diff --git a/mojo/common/time.typemap b/mojo/common/time.typemap new file mode 100644 index 0000000000000000000000000000000000000000..99e9e3ae8d8dcbc3d57af23a4100c9f5efd4b194 --- /dev/null +++ b/mojo/common/time.typemap @@ -0,0 +1,20 @@ +# Copyright 2016 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. + +mojom = "//mojo/common/time.mojom" +public_headers = [ "//base/time/time.h" ] +traits_headers = [ + "//ipc/ipc_message_utils.h", + "//mojo/common/common_custom_types_struct_traits.h", +] +public_deps = [ + "//ipc", + "//mojo/common:struct_traits", +] + +type_mappings = [ + "mojo.common.mojom.Time=base::Time[copyable_pass_by_value]", + "mojo.common.mojom.TimeDelta=base::TimeDelta[copyable_pass_by_value]", + "mojo.common.mojom.TimeTicks=base::TimeTicks[copyable_pass_by_value]", +] diff --git a/mojo/common/time_struct_traits.h b/mojo/common/time_struct_traits.h new file mode 100644 index 0000000000000000000000000000000000000000..b480edb648863ebd147938723d85fe68aff4a0ee --- /dev/null +++ b/mojo/common/time_struct_traits.h @@ -0,0 +1,55 @@ +// 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. + +#ifndef MOJO_COMMON_TIME_STRUCT_TRAITS_H_ +#define MOJO_COMMON_TIME_STRUCT_TRAITS_H_ + +#include "base/time/time.h" +#include "mojo/common/time.mojom-shared.h" + +namespace mojo { + +template <> +struct StructTraits { + static int64_t internal_value(const base::Time& time) { + return time.since_origin().InMicroseconds(); + } + + static bool Read(common::mojom::TimeDataView data, base::Time* time) { + *time = + base::Time() + base::TimeDelta::FromMicroseconds(data.internal_value()); + return true; + } +}; + +template <> +struct StructTraits { + static int64_t microseconds(const base::TimeDelta& delta) { + return delta.InMicroseconds(); + } + + static bool Read(common::mojom::TimeDeltaDataView data, + base::TimeDelta* delta) { + *delta = base::TimeDelta::FromMicroseconds(data.microseconds()); + return true; + } +}; + +template <> +struct StructTraits { + static int64_t internal_value(const base::TimeTicks& time) { + return time.since_origin().InMicroseconds(); + } + + static bool Read(common::mojom::TimeTicksDataView data, + base::TimeTicks* time) { + *time = base::TimeTicks() + + base::TimeDelta::FromMicroseconds(data.internal_value()); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_COMMON_TIME_STRUCT_TRAITS_H_ diff --git a/mojo/common/traits_test_service.mojom b/mojo/common/traits_test_service.mojom new file mode 100644 index 0000000000000000000000000000000000000000..7659eea8abb28f40eebcab517a0c082369f106f7 --- /dev/null +++ b/mojo/common/traits_test_service.mojom @@ -0,0 +1,14 @@ +// Copyright 2016 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. + +module mojo.common.mojom; + +import "mojo/common/version.mojom"; + +// All functions on this interface echo their arguments to test StructTraits +// serialization and deserialization. +interface TraitsTestService { + [Sync] + EchoVersion(Version? v) => (Version? pass); +}; diff --git a/mojo/common/typemaps.gni b/mojo/common/typemaps.gni new file mode 100644 index 0000000000000000000000000000000000000000..ae360310f18f5778edb30908102dbf3a9155849d --- /dev/null +++ b/mojo/common/typemaps.gni @@ -0,0 +1,14 @@ +# Copyright 2016 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. + +typemaps = [ + "//mojo/common/file.typemap", + "//mojo/common/file_path.typemap", + "//mojo/common/string16.typemap", + "//mojo/common/text_direction.typemap", + "//mojo/common/time.typemap", + "//mojo/common/unguessable_token.typemap", + "//mojo/common/values.typemap", + "//mojo/common/version.typemap", +] diff --git a/mojo/common/unguessable_token.mojom b/mojo/common/unguessable_token.mojom new file mode 100644 index 0000000000000000000000000000000000000000..32797171caf1f9dc3f7cd04b41adb8f32a9ab197 --- /dev/null +++ b/mojo/common/unguessable_token.mojom @@ -0,0 +1,11 @@ +// Copyright 2016 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. + +module mojo.common.mojom; + +// Corresponds to |base::UnguessableToken| in base/unguessable_token.h +struct UnguessableToken { + uint64 high; + uint64 low; +}; diff --git a/mojo/common/unguessable_token.typemap b/mojo/common/unguessable_token.typemap new file mode 100644 index 0000000000000000000000000000000000000000..ec7b1942b34540e16456ee11924717d4548c6b58 --- /dev/null +++ b/mojo/common/unguessable_token.typemap @@ -0,0 +1,12 @@ +# Copyright 2016 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. + +mojom = "//mojo/common/unguessable_token.mojom" +public_headers = [ "//base/unguessable_token.h" ] +traits_headers = [ "//mojo/common/common_custom_types_struct_traits.h" ] +public_deps = [ + "//mojo/common:struct_traits", +] + +type_mappings = [ "mojo.common.mojom.UnguessableToken=base::UnguessableToken" ] diff --git a/mojo/common/values.mojom b/mojo/common/values.mojom new file mode 100644 index 0000000000000000000000000000000000000000..722198c56ac51d45c444576d55b4d0346e9a1d0b --- /dev/null +++ b/mojo/common/values.mojom @@ -0,0 +1,32 @@ +// Copyright 2016 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. + +module mojo.common.mojom; + +union Value { + NullValue? null_value; + bool bool_value; + int32 int_value; + double double_value; + string string_value; + array binary_value; + DictionaryValue dictionary_value; + ListValue list_value; +}; + +struct ListValue { + array values; +}; + +struct DictionaryValue { + map values; +}; + +// An empty struct representing a null base::Value. +struct NullValue { +}; + +// To avoid versioning problems for arc. TODO(sammc): Remove ASAP. +[Native] +struct LegacyListValue; diff --git a/mojo/common/values.typemap b/mojo/common/values.typemap new file mode 100644 index 0000000000000000000000000000000000000000..f1f3fc2772fb055951c0b28f4f4321eccd75506e --- /dev/null +++ b/mojo/common/values.typemap @@ -0,0 +1,25 @@ +# Copyright 2016 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. + +mojom = "//mojo/common/values.mojom" +public_headers = [ "//base/values.h" ] +traits_headers = [ + "//ipc/ipc_message_utils.h", + "//mojo/common/values_struct_traits.h", +] +public_deps = [ + "//base", + "//ipc", +] +sources = [ + "values_struct_traits.cc", + "values_struct_traits.h", +] + +type_mappings = [ + "mojo.common.mojom.DictionaryValue=std::unique_ptr[move_only,nullable_is_same_type]", + "mojo.common.mojom.LegacyListValue=base::ListValue[non_copyable_non_movable]", + "mojo.common.mojom.ListValue=std::unique_ptr[move_only,nullable_is_same_type]", + "mojo.common.mojom.Value=std::unique_ptr[move_only,nullable_is_same_type]", +] diff --git a/mojo/common/values_struct_traits.cc b/mojo/common/values_struct_traits.cc new file mode 100644 index 0000000000000000000000000000000000000000..6af7a395338804f1c9b8b0eda06ee5ee8ca403d0 --- /dev/null +++ b/mojo/common/values_struct_traits.cc @@ -0,0 +1,109 @@ +// Copyright 2016 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. + +#include "base/memory/ptr_util.h" +#include "mojo/common/values_struct_traits.h" + +namespace mojo { + +bool StructTraits>:: + Read(common::mojom::ListValueDataView data, + std::unique_ptr* value_out) { + mojo::ArrayDataView view; + data.GetValuesDataView(&view); + + auto list_value = base::MakeUnique(); + for (size_t i = 0; i < view.size(); ++i) { + std::unique_ptr value; + if (!view.Read(i, &value)) + return false; + + list_value->Append(std::move(value)); + } + *value_out = std::move(list_value); + return true; +} + +bool StructTraits>:: + Read(common::mojom::DictionaryValueDataView data, + std::unique_ptr* value_out) { + mojo::MapDataView view; + data.GetValuesDataView(&view); + auto dictionary_value = base::MakeUnique(); + for (size_t i = 0; i < view.size(); ++i) { + base::StringPiece key; + std::unique_ptr value; + if (!view.keys().Read(i, &key) || !view.values().Read(i, &value)) + return false; + + dictionary_value->SetWithoutPathExpansion(key, std::move(value)); + } + *value_out = std::move(dictionary_value); + return true; +} + +std::unique_ptr +CloneTraits, false>::Clone( + const std::unique_ptr& input) { + auto result = base::MakeUnique(); + result->MergeDictionary(input.get()); + return result; +} + +bool UnionTraits>:: + Read(common::mojom::ValueDataView data, + std::unique_ptr* value_out) { + switch (data.tag()) { + case common::mojom::ValueDataView::Tag::NULL_VALUE: { + *value_out = base::Value::CreateNullValue(); + return true; + } + case common::mojom::ValueDataView::Tag::BOOL_VALUE: { + *value_out = base::MakeUnique(data.bool_value()); + return true; + } + case common::mojom::ValueDataView::Tag::INT_VALUE: { + *value_out = base::MakeUnique(data.int_value()); + return true; + } + case common::mojom::ValueDataView::Tag::DOUBLE_VALUE: { + *value_out = base::MakeUnique(data.double_value()); + return true; + } + case common::mojom::ValueDataView::Tag::STRING_VALUE: { + base::StringPiece string_value; + if (!data.ReadStringValue(&string_value)) + return false; + *value_out = base::MakeUnique(string_value); + return true; + } + case common::mojom::ValueDataView::Tag::BINARY_VALUE: { + mojo::ArrayDataView binary_data; + data.GetBinaryValueDataView(&binary_data); + *value_out = base::BinaryValue::CreateWithCopiedBuffer( + reinterpret_cast(binary_data.data()), + binary_data.size()); + return true; + } + case common::mojom::ValueDataView::Tag::DICTIONARY_VALUE: { + std::unique_ptr dictionary_value; + if (!data.ReadDictionaryValue(&dictionary_value)) + return false; + *value_out = std::move(dictionary_value); + return true; + } + case common::mojom::ValueDataView::Tag::LIST_VALUE: { + std::unique_ptr list_value; + if (!data.ReadListValue(&list_value)) + return false; + *value_out = std::move(list_value); + return true; + } + } + return false; +} + +} // namespace mojo diff --git a/mojo/common/values_struct_traits.h b/mojo/common/values_struct_traits.h new file mode 100644 index 0000000000000000000000000000000000000000..befcf3a6ea2816dd56e56f0a50933d9cec3548e0 --- /dev/null +++ b/mojo/common/values_struct_traits.h @@ -0,0 +1,257 @@ +// Copyright 2016 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 MOJO_COMMON_VALUES_STRUCT_TRAITS_H_ +#define MOJO_COMMON_VALUES_STRUCT_TRAITS_H_ + +#include "base/values.h" +#include "mojo/common/values.mojom.h" +#include "mojo/public/cpp/bindings/array_traits.h" +#include "mojo/public/cpp/bindings/clone_traits.h" +#include "mojo/public/cpp/bindings/map_traits.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/union_traits.h" + +namespace mojo { + +template <> +struct ArrayTraits { + using Element = std::unique_ptr; + using ConstIterator = base::ListValue::const_iterator; + + static size_t GetSize(const base::ListValue& input) { + return input.GetSize(); + } + + static ConstIterator GetBegin(const base::ListValue& input) { + return input.begin(); + } + + static void AdvanceIterator(ConstIterator& iterator) { ++iterator; } + + static const Element& GetValue(ConstIterator& iterator) { return *iterator; } +}; + +template <> +struct StructTraits { + static const base::ListValue& values(const base::ListValue& value) { + return value; + } +}; + +template <> +struct StructTraits> { + static bool IsNull(const std::unique_ptr& value) { + return !value; + } + + static void SetToNull(std::unique_ptr* value) { + value->reset(); + } + + static const base::ListValue& values( + const std::unique_ptr& value) { + return *value; + } + + static bool Read(common::mojom::ListValueDataView data, + std::unique_ptr* value); +}; + +template <> +struct MapTraits { + using Key = std::string; + using Value = base::Value; + using Iterator = base::DictionaryValue::Iterator; + + static size_t GetSize(const base::DictionaryValue& input) { + return input.size(); + } + + static Iterator GetBegin(const base::DictionaryValue& input) { + return Iterator(input); + } + + static void AdvanceIterator(Iterator& iterator) { iterator.Advance(); } + + static const Key& GetKey(Iterator& iterator) { return iterator.key(); } + + static const Value& GetValue(Iterator& iterator) { return iterator.value(); } +}; + +template <> +struct StructTraits { + static const base::DictionaryValue& values( + const base::DictionaryValue& value) { + return value; + } +}; + +template <> +struct StructTraits> { + static bool IsNull(const std::unique_ptr& value) { + return !value; + } + + static void SetToNull(std::unique_ptr* value) { + value->reset(); + } + + static const base::DictionaryValue& values( + const std::unique_ptr& value) { + return *value; + } + static bool Read(common::mojom::DictionaryValueDataView data, + std::unique_ptr* value); +}; + +template <> +struct CloneTraits, false> { + static std::unique_ptr Clone( + const std::unique_ptr& input); +}; + +template <> +struct UnionTraits { + static common::mojom::ValueDataView::Tag GetTag(const base::Value& data) { + switch (data.GetType()) { + case base::Value::Type::NONE: + return common::mojom::ValueDataView::Tag::NULL_VALUE; + case base::Value::Type::BOOLEAN: + return common::mojom::ValueDataView::Tag::BOOL_VALUE; + case base::Value::Type::INTEGER: + return common::mojom::ValueDataView::Tag::INT_VALUE; + case base::Value::Type::DOUBLE: + return common::mojom::ValueDataView::Tag::DOUBLE_VALUE; + case base::Value::Type::STRING: + return common::mojom::ValueDataView::Tag::STRING_VALUE; + case base::Value::Type::BINARY: + return common::mojom::ValueDataView::Tag::BINARY_VALUE; + case base::Value::Type::DICTIONARY: + return common::mojom::ValueDataView::Tag::DICTIONARY_VALUE; + case base::Value::Type::LIST: + return common::mojom::ValueDataView::Tag::LIST_VALUE; + } + NOTREACHED(); + return common::mojom::ValueDataView::Tag::NULL_VALUE; + } + + static common::mojom::NullValuePtr null_value(const base::Value& value) { + return common::mojom::NullValuePtr(); + } + + static bool bool_value(const base::Value& value) { + bool bool_value{}; + if (!value.GetAsBoolean(&bool_value)) + NOTREACHED(); + return bool_value; + } + + static int32_t int_value(const base::Value& value) { + int int_value{}; + if (!value.GetAsInteger(&int_value)) + NOTREACHED(); + return int_value; + } + + static double double_value(const base::Value& value) { + double double_value{}; + if (!value.GetAsDouble(&double_value)) + NOTREACHED(); + return double_value; + } + + static base::StringPiece string_value(const base::Value& value) { + base::StringPiece string_piece; + if (!value.GetAsString(&string_piece)) + NOTREACHED(); + return string_piece; + } + + static mojo::ConstCArray binary_value(const base::Value& value) { + const base::BinaryValue* binary_value = nullptr; + if (!value.GetAsBinary(&binary_value)) + NOTREACHED(); + return mojo::ConstCArray( + binary_value->GetSize(), + reinterpret_cast(binary_value->GetBuffer())); + } + + static const base::ListValue& list_value(const base::Value& value) { + const base::ListValue* list_value = nullptr; + if (!value.GetAsList(&list_value)) + NOTREACHED(); + return *list_value; + } + static const base::DictionaryValue& dictionary_value( + const base::Value& value) { + const base::DictionaryValue* dictionary_value = nullptr; + if (!value.GetAsDictionary(&dictionary_value)) + NOTREACHED(); + return *dictionary_value; + } +}; + +template <> +struct UnionTraits> { + static bool IsNull(const std::unique_ptr& value) { + return !value; + } + + static void SetToNull(std::unique_ptr* value) { value->reset(); } + + static common::mojom::ValueDataView::Tag GetTag( + const std::unique_ptr& value) { + return UnionTraits::GetTag( + *value); + } + + static common::mojom::NullValuePtr null_value( + const std::unique_ptr& value) { + return UnionTraits::null_value( + *value); + } + static bool bool_value(const std::unique_ptr& value) { + return UnionTraits::bool_value( + *value); + } + static int32_t int_value(const std::unique_ptr& value) { + return UnionTraits::int_value( + *value); + } + static double double_value(const std::unique_ptr& value) { + return UnionTraits::double_value( + *value); + } + static base::StringPiece string_value( + const std::unique_ptr& value) { + return UnionTraits::string_value( + *value); + } + static mojo::ConstCArray binary_value( + const std::unique_ptr& value) { + return UnionTraits::binary_value( + *value); + } + static const base::ListValue& list_value( + const std::unique_ptr& value) { + return UnionTraits::list_value( + *value); + } + static const base::DictionaryValue& dictionary_value( + const std::unique_ptr& value) { + return UnionTraits::dictionary_value(*value); + } + + static bool Read(common::mojom::ValueDataView data, + std::unique_ptr* value); +}; + +} // namespace mojo + +#endif // MOJO_COMMON_VALUES_STRUCT_TRAITS_H_ diff --git a/mojo/common/version.mojom b/mojo/common/version.mojom new file mode 100644 index 0000000000000000000000000000000000000000..6ddf6e6b8cd0acd03fecb863db46a150e0637d53 --- /dev/null +++ b/mojo/common/version.mojom @@ -0,0 +1,10 @@ +// Copyright 2016 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. + +module mojo.common.mojom; + +// Corresponds to |base::Version| in base/version.h +struct Version { + array components; +}; diff --git a/mojo/common/version.typemap b/mojo/common/version.typemap new file mode 100644 index 0000000000000000000000000000000000000000..fa7fed9acfd2d26e23a7e09bb297ab5b4b42789b --- /dev/null +++ b/mojo/common/version.typemap @@ -0,0 +1,12 @@ +# Copyright 2016 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. + +mojom = "//mojo/common/version.mojom" +public_headers = [ "//base/version.h" ] +traits_headers = [ "//mojo/common/common_custom_types_struct_traits.h" ] +public_deps = [ + "//mojo/common:struct_traits", +] + +type_mappings = [ "mojo.common.mojom.Version=base::Version" ] diff --git a/mojo/edk/DEPS b/mojo/edk/DEPS new file mode 100644 index 0000000000000000000000000000000000000000..77abb21ee3b2ba357bcf97ad581450f365758f1a --- /dev/null +++ b/mojo/edk/DEPS @@ -0,0 +1,14 @@ +include_rules = [ + # This code is checked into the chromium repo so it's fine to depend on this. + "+base", + "+crypto", + "+build", + "+gin", + "+native_client/src/public", + "+testing", + "+third_party/ashmem", + "+v8", + + # internal includes. + "+mojo", +] diff --git a/mojo/edk/embedder/BUILD.gn b/mojo/edk/embedder/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..8105bedb5ff2a0659fb45dd71dbe6c57f7dfacbf --- /dev/null +++ b/mojo/edk/embedder/BUILD.gn @@ -0,0 +1,147 @@ +# Copyright 2014 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/nacl/config.gni") + +source_set("headers") { + sources = [ + "configuration.h", + "connection_params.h", + "embedder.h", + "embedder_internal.h", + "named_platform_channel_pair.h", + "named_platform_handle.h", + "named_platform_handle_utils.h", + "pending_process_connection.h", + "platform_channel_pair.h", + "platform_handle.h", + "platform_handle_utils.h", + "scoped_platform_handle.h", + ] + + public_deps = [ + "//base", + "//mojo/public/cpp/system", + ] +} + +source_set("embedder") { + # This isn't really a standalone target; it must be linked into the + # mojo_system_impl component. + visibility = [ + "//mojo/edk/system", + "//components/nacl:nacl", + ] + + sources = [ + "configuration.h", + "connection_params.cc", + "connection_params.h", + "embedder.cc", + "embedder.h", + "embedder_internal.h", + "entrypoints.cc", + "entrypoints.h", + "pending_process_connection.cc", + "scoped_ipc_support.cc", + "scoped_ipc_support.h", + + # Test-only code: + # TODO(vtl): It's a little unfortunate that these end up in the same + # component as non-test-only code. In the static build, this code should + # hopefully be dead-stripped. + "test_embedder.cc", + "test_embedder.h", + ] + + defines = [ "MOJO_SYSTEM_IMPL_IMPLEMENTATION" ] + + public_deps = [ + ":headers", + ":platform", + "//base", + "//mojo/public/cpp/system", + ] + + if (!is_nacl) { + deps = [ + "//crypto", + ] + } +} + +source_set("platform") { + # This isn't really a standalone target; it must be linked into the + # mojo_system_impl component. + visibility = [ + ":embedder", + "//mojo/edk/system", + ] + + sources = [ + "named_platform_channel_pair.h", + "named_platform_channel_pair_win.cc", + "named_platform_handle.h", + "named_platform_handle_utils.h", + "named_platform_handle_utils_win.cc", + "platform_channel_pair.cc", + "platform_channel_pair.h", + "platform_channel_pair_posix.cc", + "platform_channel_pair_win.cc", + "platform_channel_utils_posix.cc", + "platform_channel_utils_posix.h", + "platform_handle.cc", + "platform_handle.h", + "platform_handle_utils.h", + "platform_handle_utils_posix.cc", + "platform_handle_utils_win.cc", + "platform_handle_vector.h", + "platform_shared_buffer.cc", + "platform_shared_buffer.h", + "scoped_platform_handle.h", + ] + if (!is_nacl) { + sources += [ "named_platform_handle_utils_posix.cc" ] + } + + defines = [ "MOJO_SYSTEM_IMPL_IMPLEMENTATION" ] + + public_deps = [ + "//mojo/public/cpp/system", + ] + + deps = [ + "//base", + ] + + if (is_android) { + deps += [ "//third_party/ashmem" ] + } + + if (is_nacl && !is_nacl_nonsfi) { + sources -= [ "platform_channel_utils_posix.cc" ] + } +} + +source_set("embedder_unittests") { + testonly = true + + # TODO: Figure out why this visibility check fails on Android. + # visibility = [ "//mojo/edk/system:mojo_system_unittests" ] + + sources = [ + "embedder_unittest.cc", + "platform_channel_pair_posix_unittest.cc", + "platform_shared_buffer_unittest.cc", + ] + + deps = [ + "//base", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/edk/system:test_utils", + "//mojo/edk/test:test_support", + "//testing/gtest", + ] +} diff --git a/mojo/edk/embedder/README.md b/mojo/edk/embedder/README.md new file mode 100644 index 0000000000000000000000000000000000000000..fc53beceea772756139a585d8cbef6b45a86ab62 --- /dev/null +++ b/mojo/edk/embedder/README.md @@ -0,0 +1,346 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Embedder Development Kit (EDK) +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview + +The Mojo EDK is a (binary-unstable) API which enables a process to use Mojo both +internally and for IPC to other Mojo-embedding processes. + +Using any of the API surface in `//mojo/edk/embedder` requires (somewhat +confusingly) a direct dependency on the GN `//mojo/edk/system` target. Despite +this fact, you should never reference any of the headers in `mojo/edk/system` +directly, as everything there is considered to be an internal detail of the EDK. + +**NOTE:** Unless you are introducing a new binary entry point into the system +(*e.g.,* a new executable with a new `main()` definition), you probably don't +need to know anything about the EDK API. Most processes defined in the Chrome +repo today already fully initialize the EDK so that Mojo's other public APIs +"just work" out of the box. + +## Basic Initialization + +In order to use Mojo in a given process, it's necessary to call +`mojo::edk::Init` exactly once: + +``` +#include "mojo/edk/embedder/embedder.h" + +int main(int argc, char** argv) { + mojo::edk::Init(); + + // Now you can create message pipes, write messages, etc + + return 0; +} +``` + +As it happens though, Mojo is less useful without some kind of IPC support as +well, and that's a second initialization step. + +## IPC Initialization + +You also need to provide the system with a background TaskRunner on which it can +watch for inbound I/O from any of the various other processes you will later +connect to it. + +Here we'll just create a new background thread for IPC and let Mojo use that. +Note that in Chromium, we use the existing "IO thread" in the browser process +and content child processes. + +``` +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" + +int main(int argc, char** argv) { + mojo::edk::Init(); + + base::Thread ipc_thread("ipc!"); + ipc_thread.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO)); + + // As long as this object is alive, all EDK API surface relevant to IPC + // connections is usable and message pipes which span a process boundary will + // continue to function. + mojo::edk::ScopedIPCSupport ipc_support( + ipc_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + + return 0; +} +``` + +This process is now fully prepared to use Mojo IPC! + +Note that all existing process types in Chromium already perform this setup +very early during startup. + +## Connecting Two Processes + +Now suppose you're running a process which has initialized Mojo IPC, and you +want to launch another process which you know will also initialize Mojo IPC. +You want to be able to connect Mojo interfaces between these two processes. +Rejoice, because this section was written just for you. + +NOTE: For legacy reasons, some API terminology may refer to concepts of "parent" +and "child" as a relationship between processes being connected by Mojo. This +relationship is today completely orthogonal to any notion of process hierarchy +in the OS, and so use of these APIs is not constrained by an adherence to any +such hierarchy. + +Mojo requires you to bring your own OS pipe to the party, and it will do the +rest. It also provides a convenient mechanism for creating such pipes, known as +a `PlatformChannelPair`. + +You provide one end of this pipe to the EDK in the local process via +`PendingProcessConnection` - which can also be used to create cross-process +message pipes (see the next section) - and you're responsible for getting the +other end into the remote process. + +``` +#include "base/process/process_handle.h" +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/pending_process_connection.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" + +// You write this. It launches a new process, passing the pipe handle +// encapsulated by |channel| by any means possible (e.g. on Windows or POSIX +// you may inhert the file descriptor/HANDLE at launch and pass a commandline +// argument to indicate its numeric value). Returns the handle of the new +// process. +base::ProcessHandle LaunchCoolChildProcess( + mojo::edk::ScopedPlatformHandle channel); + +int main(int argc, char** argv) { + mojo::edk::Init(); + + base::Thread ipc_thread("ipc!"); + ipc_thread.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO)); + + mojo::edk::ScopedIPCSupport ipc_support( + ipc_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + + // This is essentially always an OS pipe (domain socket pair, Windows named + // pipe, etc.) + mojo::edk::PlatformChannelPair channel; + + // This is a scoper which encapsulates the intent to connect to another + // process. It exists because process connection is inherently asynchronous, + // things may go wrong, and the lifetime of any associated resources is bound + // by the lifetime of this object regardless of success or failure. + mojo::edk::PendingProcessConnection child; + + base::ProcessHandle child_handle = + LaunchCoolChildProcess(channel.PassClientHandle()); + + // At this point it's safe for |child| to go out of scope and nothing will + // break. + child.Connect(child_handle, channel.PassServerHandle()); + + return 0; +} +``` + +The launched process code uses `SetParentPipeHandle` to get connected, and might +look something like: + +``` +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" + +// You write this. It acquires the ScopedPlatformHandle that was passed by +// whomever launched this process (i.e. LaunchCoolChildProcess above). +mojo::edk::ScopedPlatformHandle GetChannelHandle(); + +int main(int argc, char** argv) { + mojo::edk::Init(); + + base::Thread ipc_thread("ipc!"); + ipc_thread.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO)); + + mojo::edk::ScopedIPCSupport ipc_support( + ipc_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + + mojo::edk::SetParentPipeHandle(GetChannelHandle()); + + return 0; +} +``` + +Now you have IPC initialized between two processes. For some practical examples +of how this is done, you can dig into the various multiprocess tests in the +`mojo_system_unittests` test suite. + +## Bootstrapping Cross-Process Message Pipes + +Having internal Mojo IPC support initialized is pretty useless if you don't have +any message pipes spanning the process boundary. Fortunately, this is made +trivial by the EDK: `PendingProcessConnection` has a +`CreateMessagePipe` method which synthesizes a new solitary message pipe +endpoint for your immediate use, while also generating a magic token string that +can be exchanged for the other end of the pipe via +`mojo::edk::CreateChildMessagePipe`. + +The token exchange can be done by the same process (which is sometimes useful), +or by the process that is eventually connected via `Connect()` on that +`PendingProcessConnection`. This means that you can effectively pass message +pipes on the commandline by passing a token string. + +We can modify our existing sample code as follows: + +``` +#include "base/command_line.h" +#include "base/process/process_handle.h" +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/pending_process_connection.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "local/foo.mojom.h" // You provide this + +base::ProcessHandle LaunchCoolChildProcess( + const base::CommandLine& command_line, + mojo::edk::ScopedPlatformHandle channel); + +int main(int argc, char** argv) { + mojo::edk::Init(); + + base::Thread ipc_thread("ipc!"); + ipc_thread.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO)); + + mojo::edk::ScopedIPCSupport ipc_support( + ipc_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + + mojo::edk::PlatformChannelPair channel; + + mojo::edk::PendingProcessConnection child; + + base::CommandLine command_line; // Assume this is appropriately initialized + + // Create a new message pipe with one end being retrievable in the new + // process. Note that it doesn't matter whether we call CreateMessagePipe() + // before or after Connect(), and we can create as many different pipes as + // we like. + std::string pipe_token; + mojo::ScopedMessagePipeHandle my_pipe = child.CreateMessagePipe(&pipe_token); + command_line.AppendSwitchASCII("primordial-pipe", pipe_token); + + base::ProcessHandle child_handle = + LaunchCoolChildProcess(command_line, channel.PassClientHandle()); + + child.Connect(child_handle, channel.PassServerHandle()); + + // We can start using our end of the pipe immediately. Here we assume the + // other end will eventually be bound to a local::mojom::Foo implementation, + // so we can start making calls on that interface. + // + // Note that this could even be done before the child process is launched and + // it would still work as expected. + local::mojom::FooPtr foo; + foo.Bind(local::mojom::FooPtrInfo(std::move(my_pipe), 0)); + foo->DoSomeStuff(42); + + return 0; +} +``` + +and for the launched process: + + +``` +#include "base/command_line.h" +#include "base/run_loop/run_loop.h" +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "local/foo.mojom.h" // You provide this + +mojo::edk::ScopedPlatformHandle GetChannelHandle(); + +class FooImpl : local::mojom::Foo { + public: + explicit FooImpl(local::mojom::FooRequest request) + : binding_(this, std::move(request)) {} + ~FooImpl() override {} + + void DoSomeStuff(int32_t n) override { + // ... + } + + private: + mojo::Binding binding_; + + DISALLOW_COPY_AND_ASSIGN(FooImpl); +}; + +int main(int argc, char** argv) { + base::CommandLine::Init(argc, argv); + + mojo::edk::Init(); + + base::Thread ipc_thread("ipc!"); + ipc_thread.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO)); + + mojo::edk::ScopedIPCSupport ipc_support( + ipc_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + + mojo::edk::SetParentPipeHandle(GetChannelHandle()); + + mojo::ScopedMessagePipeHandle my_pipe = mojo::edk::CreateChildMessagePipe( + base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + "primordial-pipe")); + + local::mojom::FooRequest foo_request; + foo_request.Bind(std::move(my_pipe)); + FooImpl impl(std::move(foo_request)); + + // Run forever! + base::RunLoop().Run(); + + return 0; +} +``` + +Note that the above samples assume an interface definition in +`//local/test.mojom` which would look something like: + +``` +module local.mojom; + +interface Foo { + DoSomeStuff(int32 n); +}; +``` + +Once you've bootstrapped your process connection with a real mojom interface, +you can avoid any further mucking around with EDK APIs or raw message pipe +handles, as everything beyond this point - including the passing of other +interface pipes - can be handled eloquently using +[public bindings APIs](/mojo#High-Level-Bindings-APIs). + +## Setting System Properties + +The public Mojo C System API exposes a +[**`MojoGetProperty`**](/mojo/public/c/system#MojoGetProperty) function for +querying global, embedder-defined property values. These can be set by calling: + +``` +mojo::edk::SetProperty(MojoPropertyType type, const void* value) +``` + diff --git a/mojo/edk/embedder/configuration.h b/mojo/edk/embedder/configuration.h new file mode 100644 index 0000000000000000000000000000000000000000..1990fb130dcf0abeb0c54e821c616fb372f590a4 --- /dev/null +++ b/mojo/edk/embedder/configuration.h @@ -0,0 +1,65 @@ +// Copyright 2014 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 MOJO_EDK_EMBEDDER_CONFIGURATION_H_ +#define MOJO_EDK_EMBEDDER_CONFIGURATION_H_ + +#include +#include + +namespace mojo { +namespace edk { + +// A set of constants that the Mojo system internally uses. These values should +// be consistent across all processes on the same system. +// +// In general, there should be no need to change these values from their +// defaults. However, if you do change them, you must do so before +// initialization. +struct Configuration { + // Maximum number of open (Mojo) handles. The default is 1,000,000. + // + // TODO(vtl): This doesn't count "live" handles, some of which may live in + // messages. + size_t max_handle_table_size; + + // Maximum number of active memory mappings. The default is 1,000,000. + size_t max_mapping_table_sze; + + // Maximum data size of messages sent over message pipes, in bytes. The + // default is 4MB. + size_t max_message_num_bytes; + + // Maximum number of handles that can be attached to messages sent over + // message pipes. The default is 10,000. + size_t max_message_num_handles; + + // Maximum capacity of a data pipe, in bytes. The default is 256MB. This value + // must fit into a |uint32_t|. WARNING: If you bump it closer to 2^32, you + // must audit all the code to check that we don't overflow (2^31 would + // definitely be risky; up to 2^30 is probably okay). + size_t max_data_pipe_capacity_bytes; + + // Default data pipe capacity, if not specified explicitly in the creation + // options. The default is 1MB. + size_t default_data_pipe_capacity_bytes; + + // Alignment for the "start" of the data buffer used by data pipes. (The + // alignment of elements will depend on this and the element size.) The + // default is 16 bytes. + size_t data_pipe_buffer_alignment_bytes; + + // Maximum size of a single shared memory segment, in bytes. The default is + // 1GB. + // + // TODO(vtl): Set this hard limit appropriately (e.g., higher on 64-bit). + // (This will also entail some auditing to make sure I'm not messing up my + // checks anywhere.) + size_t max_shared_memory_num_bytes; +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_CONFIGURATION_H_ diff --git a/mojo/edk/embedder/connection_params.cc b/mojo/edk/embedder/connection_params.cc new file mode 100644 index 0000000000000000000000000000000000000000..9b7ec54eb4ea211897aad60995f8b6d6f198689a --- /dev/null +++ b/mojo/edk/embedder/connection_params.cc @@ -0,0 +1,28 @@ +// 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. + +#include "mojo/edk/embedder/connection_params.h" + +#include + +namespace mojo { +namespace edk { + +ConnectionParams::ConnectionParams(ScopedPlatformHandle channel) + : channel_(std::move(channel)) {} + +ConnectionParams::ConnectionParams(ConnectionParams&& param) + : channel_(std::move(param.channel_)) {} + +ConnectionParams& ConnectionParams::operator=(ConnectionParams&& param) { + channel_ = std::move(param.channel_); + return *this; +} + +ScopedPlatformHandle ConnectionParams::TakeChannelHandle() { + return std::move(channel_); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/connection_params.h b/mojo/edk/embedder/connection_params.h new file mode 100644 index 0000000000000000000000000000000000000000..25ffdde7ab4a443d756ac42016576668f8d5682d --- /dev/null +++ b/mojo/edk/embedder/connection_params.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef MOJO_EDK_EMBEDDER_CONNECTION_PARAMS_H_ +#define MOJO_EDK_EMBEDDER_CONNECTION_PARAMS_H_ + +#include "base/macros.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +class MOJO_SYSTEM_IMPL_EXPORT ConnectionParams { + public: + explicit ConnectionParams(ScopedPlatformHandle channel); + + ConnectionParams(ConnectionParams&& param); + ConnectionParams& operator=(ConnectionParams&& param); + + ScopedPlatformHandle TakeChannelHandle(); + + private: + ScopedPlatformHandle channel_; + + DISALLOW_COPY_AND_ASSIGN(ConnectionParams); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_CONNECTION_PARAMS_H_ diff --git a/mojo/edk/embedder/embedder.cc b/mojo/edk/embedder/embedder.cc new file mode 100644 index 0000000000000000000000000000000000000000..0fdda5cd1c72bbf512f56ba07ab5f681b5f43241 --- /dev/null +++ b/mojo/edk/embedder/embedder.cc @@ -0,0 +1,158 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/embedder.h" + +#include +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/entrypoints.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/node_controller.h" + +#if !defined(OS_NACL) +#include "crypto/random.h" +#endif + +namespace mojo { +namespace edk { + +class Core; +class PlatformSupport; + +namespace internal { + +Core* g_core; + +Core* GetCore() { return g_core; } + +} // namespace internal + +void SetMaxMessageSize(size_t bytes) { +} + +void SetParentPipeHandle(ScopedPlatformHandle pipe) { + CHECK(internal::g_core); + internal::g_core->InitChild(ConnectionParams(std::move(pipe))); +} + +void SetParentPipeHandleFromCommandLine() { + ScopedPlatformHandle platform_channel = + PlatformChannelPair::PassClientHandleFromParentProcess( + *base::CommandLine::ForCurrentProcess()); + CHECK(platform_channel.is_valid()); + SetParentPipeHandle(std::move(platform_channel)); +} + +ScopedMessagePipeHandle ConnectToPeerProcess(ScopedPlatformHandle pipe) { + return ConnectToPeerProcess(std::move(pipe), GenerateRandomToken()); +} + +ScopedMessagePipeHandle ConnectToPeerProcess(ScopedPlatformHandle pipe, + const std::string& peer_token) { + DCHECK(pipe.is_valid()); + DCHECK(!peer_token.empty()); + return internal::g_core->ConnectToPeerProcess(std::move(pipe), peer_token); +} + +void ClosePeerConnection(const std::string& peer_token) { + return internal::g_core->ClosePeerConnection(peer_token); +} + +void Init() { + MojoSystemThunks thunks = MakeSystemThunks(); + size_t expected_size = MojoEmbedderSetSystemThunks(&thunks); + DCHECK_EQ(expected_size, sizeof(thunks)); + + internal::g_core = new Core(); +} + +void SetDefaultProcessErrorCallback(const ProcessErrorCallback& callback) { + internal::g_core->SetDefaultProcessErrorCallback(callback); +} + +MojoResult CreatePlatformHandleWrapper( + ScopedPlatformHandle platform_handle, + MojoHandle* platform_handle_wrapper_handle) { + return internal::g_core->CreatePlatformHandleWrapper( + std::move(platform_handle), platform_handle_wrapper_handle); +} + +MojoResult PassWrappedPlatformHandle(MojoHandle platform_handle_wrapper_handle, + ScopedPlatformHandle* platform_handle) { + return internal::g_core->PassWrappedPlatformHandle( + platform_handle_wrapper_handle, platform_handle); +} + +MojoResult CreateSharedBufferWrapper( + base::SharedMemoryHandle shared_memory_handle, + size_t num_bytes, + bool read_only, + MojoHandle* mojo_wrapper_handle) { + return internal::g_core->CreateSharedBufferWrapper( + shared_memory_handle, num_bytes, read_only, mojo_wrapper_handle); +} + +MojoResult PassSharedMemoryHandle( + MojoHandle mojo_handle, + base::SharedMemoryHandle* shared_memory_handle, + size_t* num_bytes, + bool* read_only) { + return internal::g_core->PassSharedMemoryHandle( + mojo_handle, shared_memory_handle, num_bytes, read_only); +} + +void InitIPCSupport(scoped_refptr io_thread_task_runner) { + CHECK(internal::g_core); + internal::g_core->SetIOTaskRunner(io_thread_task_runner); +} + +scoped_refptr GetIOTaskRunner() { + return internal::g_core->GetNodeController()->io_task_runner(); +} + +void ShutdownIPCSupport(const base::Closure& callback) { + CHECK(internal::g_core); + internal::g_core->RequestShutdown(callback); +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +void SetMachPortProvider(base::PortProvider* port_provider) { + DCHECK(port_provider); + internal::g_core->SetMachPortProvider(port_provider); +} +#endif + +ScopedMessagePipeHandle CreateChildMessagePipe(const std::string& token) { + return internal::g_core->CreateChildMessagePipe(token); +} + +std::string GenerateRandomToken() { + char random_bytes[16]; +#if defined(OS_NACL) + // Not secure. For NaCl only! + base::RandBytes(random_bytes, 16); +#else + crypto::RandBytes(random_bytes, 16); +#endif + return base::HexEncode(random_bytes, 16); +} + +MojoResult SetProperty(MojoPropertyType type, const void* value) { + CHECK(internal::g_core); + return internal::g_core->SetProperty(type, value); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/embedder.h b/mojo/edk/embedder/embedder.h new file mode 100644 index 0000000000000000000000000000000000000000..97258e52f61d535bcf96f95be25ecf659fbefb56 --- /dev/null +++ b/mojo/edk/embedder/embedder.h @@ -0,0 +1,173 @@ +// Copyright 2014 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 MOJO_EDK_EMBEDDER_EMBEDDER_H_ +#define MOJO_EDK_EMBEDDER_EMBEDDER_H_ + +#include + +#include +#include + +#include "base/callback.h" +#include "base/command_line.h" +#include "base/memory/ref_counted.h" +#include "base/memory/shared_memory_handle.h" +#include "base/process/process_handle.h" +#include "base/task_runner.h" +#include "mojo/edk/embedder/pending_process_connection.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace base { +class PortProvider; +} + +namespace mojo { +namespace edk { + +// Basic configuration/initialization ------------------------------------------ + +// |Init()| sets up the basic Mojo system environment, making the |Mojo...()| +// functions available and functional. This is never shut down (except in tests +// -- see test_embedder.h). + +// Allows changing the default max message size. Must be called before Init. +MOJO_SYSTEM_IMPL_EXPORT void SetMaxMessageSize(size_t bytes); + +// Should be called as early as possible in a child process with a handle to the +// other end of a pipe provided in the parent to +// PendingProcessConnection::Connect. +MOJO_SYSTEM_IMPL_EXPORT void SetParentPipeHandle(ScopedPlatformHandle pipe); + +// Same as above but extracts the pipe handle from the command line. See +// PlatformChannelPair for details. +MOJO_SYSTEM_IMPL_EXPORT void SetParentPipeHandleFromCommandLine(); + +// Called to connect to a peer process. This should be called only if there +// is no common ancestor for the processes involved within this mojo system. +// Both processes must call this function, each passing one end of a platform +// channel. This returns one end of a message pipe to each process. +MOJO_SYSTEM_IMPL_EXPORT ScopedMessagePipeHandle +ConnectToPeerProcess(ScopedPlatformHandle pipe); + +// Called to connect to a peer process. This should be called only if there +// is no common ancestor for the processes involved within this mojo system. +// Both processes must call this function, each passing one end of a platform +// channel. This returns one end of a message pipe to each process. |peer_token| +// may be passed to ClosePeerConnection() to close the connection. +MOJO_SYSTEM_IMPL_EXPORT ScopedMessagePipeHandle +ConnectToPeerProcess(ScopedPlatformHandle pipe, const std::string& peer_token); + +// Closes a connection to a peer process created by ConnectToPeerProcess() +// where the same |peer_token| was used. +MOJO_SYSTEM_IMPL_EXPORT void ClosePeerConnection(const std::string& peer_token); + +// Must be called first, or just after setting configuration parameters, to +// initialize the (global, singleton) system. +MOJO_SYSTEM_IMPL_EXPORT void Init(); + +// Sets a default callback to invoke when an internal error is reported but +// cannot be associated with a specific child process. +MOJO_SYSTEM_IMPL_EXPORT void SetDefaultProcessErrorCallback( + const ProcessErrorCallback& callback); + +// Basic functions ------------------------------------------------------------- + +// The functions in this section are available once |Init()| has been called. + +// Creates a |MojoHandle| that wraps the given |PlatformHandle| (taking +// ownership of it). This |MojoHandle| can then, e.g., be passed through message +// pipes. Note: This takes ownership (and thus closes) |platform_handle| even on +// failure, which is different from what you'd expect from a Mojo API, but it +// makes for a more convenient embedder API. +MOJO_SYSTEM_IMPL_EXPORT MojoResult +CreatePlatformHandleWrapper(ScopedPlatformHandle platform_handle, + MojoHandle* platform_handle_wrapper_handle); + +// Retrieves the |PlatformHandle| that was wrapped into a |MojoHandle| (using +// |CreatePlatformHandleWrapper()| above). Note that the |MojoHandle| is closed +// on success. +MOJO_SYSTEM_IMPL_EXPORT MojoResult +PassWrappedPlatformHandle(MojoHandle platform_handle_wrapper_handle, + ScopedPlatformHandle* platform_handle); + +// Creates a |MojoHandle| that wraps the given |SharedMemoryHandle| (taking +// ownership of it). |num_bytes| is the size of the shared memory object, and +// |read_only| is whether the handle is a read-only handle to shared memory. +// This |MojoHandle| is a Mojo shared buffer and can be manipulated using the +// shared buffer functions and transferred over a message pipe. +MOJO_SYSTEM_IMPL_EXPORT MojoResult +CreateSharedBufferWrapper(base::SharedMemoryHandle shared_memory_handle, + size_t num_bytes, + bool read_only, + MojoHandle* mojo_wrapper_handle); + +// Retrieves the underlying |SharedMemoryHandle| from a shared buffer +// |MojoHandle| and closes the handle. If successful, |num_bytes| will contain +// the size of the shared memory buffer and |read_only| will contain whether the +// buffer handle is read-only. Both |num_bytes| and |read_only| may be null. +// Note: The value of |shared_memory_handle| may be +// base::SharedMemory::NULLHandle(), even if this function returns success. +// Callers should perform appropriate checks. +MOJO_SYSTEM_IMPL_EXPORT MojoResult +PassSharedMemoryHandle(MojoHandle mojo_handle, + base::SharedMemoryHandle* shared_memory_handle, + size_t* num_bytes, + bool* read_only); + +// Initialialization/shutdown for interprocess communication (IPC) ------------- + +// |InitIPCSupport()| sets up the subsystem for interprocess communication, +// making the IPC functions (in the following section) available and functional. +// (This may only be done after |Init()|.) +// +// This subsystem may be shut down using |ShutdownIPCSupport()|. None of the IPC +// functions may be called after this is called. +// +// |io_thread_task_runner| should live at least until |ShutdownIPCSupport()|'s +// callback has been run. +MOJO_SYSTEM_IMPL_EXPORT void InitIPCSupport( + scoped_refptr io_thread_task_runner); + +// Retrieves the TaskRunner used for IPC I/O, as set by InitIPCSupport. +MOJO_SYSTEM_IMPL_EXPORT scoped_refptr GetIOTaskRunner(); + +// Shuts down the subsystem initialized by |InitIPCSupport()|. It be called from +// any thread and will attempt to complete shutdown on the I/O thread with which +// the system was initialized. Upon completion, |callback| is invoked on an +// arbitrary thread. +MOJO_SYSTEM_IMPL_EXPORT void ShutdownIPCSupport(const base::Closure& callback); + +#if defined(OS_MACOSX) && !defined(OS_IOS) +// Set the |base::PortProvider| for this process. Can be called on any thread, +// but must be set in the root process before any Mach ports can be transferred. +MOJO_SYSTEM_IMPL_EXPORT void SetMachPortProvider( + base::PortProvider* port_provider); +#endif + +// Creates a message pipe from a token in a child process. This token must have +// been acquired by a corresponding call to +// PendingProcessConnection::CreateMessagePipe. +MOJO_SYSTEM_IMPL_EXPORT ScopedMessagePipeHandle +CreateChildMessagePipe(const std::string& token); + +// Generates a random ASCII token string for use with various APIs that expect +// a globally unique token string. +MOJO_SYSTEM_IMPL_EXPORT std::string GenerateRandomToken(); + +// Sets system properties that can be read by the MojoGetProperty() API. See the +// documentation for MojoPropertyType for supported property types and their +// corresponding value type. +// +// Default property values: +// |MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED| - true +MOJO_SYSTEM_IMPL_EXPORT MojoResult SetProperty(MojoPropertyType type, + const void* value); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_EMBEDDER_H_ diff --git a/mojo/edk/embedder/embedder_internal.h b/mojo/edk/embedder/embedder_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..7deeca157d1d831a61ae3af951bebfaf14f66997 --- /dev/null +++ b/mojo/edk/embedder/embedder_internal.h @@ -0,0 +1,44 @@ +// Copyright 2014 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. + +// This header contains internal details for the *implementation* of the +// embedder API. It should not be included by any public header (nor by users of +// the embedder API). + +#ifndef MOJO_EDK_EMBEDDER_EMBEDDER_INTERNAL_H_ +#define MOJO_EDK_EMBEDDER_EMBEDDER_INTERNAL_H_ + +#include + +#include "mojo/edk/system/system_impl_export.h" + +namespace base { +class TaskRunner; +} + +namespace mojo { + +namespace edk { + +class Broker; +class Core; +class ProcessDelegate; + +namespace internal { + +// Instance of |Broker| to use. +extern Broker* g_broker; + +// Instance of |Core| used by the system functions (|Mojo...()|). +extern MOJO_SYSTEM_IMPL_EXPORT Core* g_core; +extern base::TaskRunner* g_delegate_thread_task_runner; +extern ProcessDelegate* g_process_delegate; + +} // namespace internal + +} // namepace edk + +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_EMBEDDER_INTERNAL_H_ diff --git a/mojo/edk/embedder/embedder_unittest.cc b/mojo/edk/embedder/embedder_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..388b45c3651c807887cf5b6ecdcaf6499e1f9711 --- /dev/null +++ b/mojo/edk/embedder/embedder_unittest.cc @@ -0,0 +1,603 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/embedder.h" + +#include +#include +#include + +#include + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/shared_memory.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/process/process_handle.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/test/test_timeouts.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/named_platform_handle_utils.h" +#include "mojo/edk/embedder/pending_process_connection.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/embedder/test_embedder.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/core.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +// The multiprocess tests that use these don't compile on iOS. +#if !defined(OS_IOS) +const char kHelloWorld[] = "hello world"; +const char kByeWorld[] = "bye world"; +#endif + +using EmbedderTest = test::MojoTestBase; + +TEST_F(EmbedderTest, ChannelBasic) { + MojoHandle server_mp, client_mp; + CreateMessagePipe(&server_mp, &client_mp); + + const std::string kHello = "hello"; + + // We can write to a message pipe handle immediately. + WriteMessage(server_mp, kHello); + EXPECT_EQ(kHello, ReadMessage(client_mp)); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp)); +} + +// Verifies that a MP with pending messages to be written can be sent and the +// pending messages aren't dropped. +TEST_F(EmbedderTest, SendMessagePipeWithWriteQueue) { + MojoHandle server_mp, client_mp; + CreateMessagePipe(&server_mp, &client_mp); + + MojoHandle server_mp2, client_mp2; + CreateMessagePipe(&server_mp2, &client_mp2); + + static const size_t kNumMessages = 1001; + for (size_t i = 1; i <= kNumMessages; i++) + WriteMessage(client_mp2, std::string(i, 'A' + (i % 26))); + + // Now send client2. + WriteMessageWithHandles(server_mp, "hey", &client_mp2, 1); + client_mp2 = MOJO_HANDLE_INVALID; + + // Read client2 just so we can close it later. + EXPECT_EQ("hey", ReadMessageWithHandles(client_mp, &client_mp2, 1)); + EXPECT_NE(MOJO_HANDLE_INVALID, client_mp2); + + // Now verify that all the messages that were written were sent correctly. + for (size_t i = 1; i <= kNumMessages; i++) + ASSERT_EQ(std::string(i, 'A' + (i % 26)), ReadMessage(server_mp2)); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp2)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp2)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp)); +} + +TEST_F(EmbedderTest, ChannelsHandlePassing) { + MojoHandle server_mp, client_mp; + CreateMessagePipe(&server_mp, &client_mp); + EXPECT_NE(server_mp, MOJO_HANDLE_INVALID); + EXPECT_NE(client_mp, MOJO_HANDLE_INVALID); + + MojoHandle h0, h1; + CreateMessagePipe(&h0, &h1); + + // Write a message to |h0| (attaching nothing). + const std::string kHello = "hello"; + WriteMessage(h0, kHello); + + // Write one message to |server_mp|, attaching |h1|. + const std::string kWorld = "world!!!"; + WriteMessageWithHandles(server_mp, kWorld, &h1, 1); + h1 = MOJO_HANDLE_INVALID; + + // Write another message to |h0|. + const std::string kFoo = "foo"; + WriteMessage(h0, kFoo); + + // Wait for |client_mp| to become readable and read a message from it. + EXPECT_EQ(kWorld, ReadMessageWithHandles(client_mp, &h1, 1)); + EXPECT_NE(h1, MOJO_HANDLE_INVALID); + + // Wait for |h1| to become readable and read a message from it. + EXPECT_EQ(kHello, ReadMessage(h1)); + + // Wait for |h1| to become readable (again) and read its second message. + EXPECT_EQ(kFoo, ReadMessage(h1)); + + // Write a message to |h1|. + const std::string kBarBaz = "barbaz"; + WriteMessage(h1, kBarBaz); + + // Wait for |h0| to become readable and read a message from it. + EXPECT_EQ(kBarBaz, ReadMessage(h0)); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(h0)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(h1)); +} + +TEST_F(EmbedderTest, PipeSetup) { + // Ensures that a pending process connection's message pipe can be claimed by + // the host process itself. + PendingProcessConnection process; + std::string pipe_token; + ScopedMessagePipeHandle parent_mp = process.CreateMessagePipe(&pipe_token); + ScopedMessagePipeHandle child_mp = CreateChildMessagePipe(pipe_token); + + const std::string kHello = "hello"; + WriteMessage(parent_mp.get().value(), kHello); + + EXPECT_EQ(kHello, ReadMessage(child_mp.get().value())); +} + +TEST_F(EmbedderTest, PipeSetup_LaunchDeath) { + PlatformChannelPair pair; + + PendingProcessConnection process; + std::string pipe_token; + ScopedMessagePipeHandle parent_mp = process.CreateMessagePipe(&pipe_token); + process.Connect(base::GetCurrentProcessHandle(), + ConnectionParams(pair.PassServerHandle())); + + // Close the remote end, simulating child death before the child connects to + // the reserved port. + ignore_result(pair.PassClientHandle()); + + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(parent_mp.get().value(), + MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +TEST_F(EmbedderTest, PipeSetup_LaunchFailure) { + PlatformChannelPair pair; + + auto process = base::MakeUnique(); + std::string pipe_token; + ScopedMessagePipeHandle parent_mp = process->CreateMessagePipe(&pipe_token); + + // Ensure that if a PendingProcessConnection goes away before Connect() is + // called, any message pipes associated with it detect peer closure. + process.reset(); + + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(parent_mp.get().value(), + MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +// The sequence of messages sent is: +// server_mp client_mp mp0 mp1 mp2 mp3 +// 1. "hello" +// 2. "world!" +// 3. "FOO" +// 4. "Bar"+mp1 +// 5. (close) +// 6. (close) +// 7. "baz" +// 8. (closed) +// 9. "quux"+mp2 +// 10. (close) +// 11. (wait/cl.) +// 12. (wait/cl.) + +#if !defined(OS_IOS) + +TEST_F(EmbedderTest, MultiprocessChannels) { + RUN_CHILD_ON_PIPE(MultiprocessChannelsClient, server_mp) + // 1. Write a message to |server_mp| (attaching nothing). + WriteMessage(server_mp, "hello"); + + // 2. Read a message from |server_mp|. + EXPECT_EQ("world!", ReadMessage(server_mp)); + + // 3. Create a new message pipe (endpoints |mp0| and |mp1|). + MojoHandle mp0, mp1; + CreateMessagePipe(&mp0, &mp1); + + // 4. Write something to |mp0|. + WriteMessage(mp0, "FOO"); + + // 5. Write a message to |server_mp|, attaching |mp1|. + WriteMessageWithHandles(server_mp, "Bar", &mp1, 1); + mp1 = MOJO_HANDLE_INVALID; + + // 6. Read a message from |mp0|, which should have |mp2| attached. + MojoHandle mp2 = MOJO_HANDLE_INVALID; + EXPECT_EQ("quux", ReadMessageWithHandles(mp0, &mp2, 1)); + + // 7. Read a message from |mp2|. + EXPECT_EQ("baz", ReadMessage(mp2)); + + // 8. Close |mp0|. + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp0)); + + // 9. Tell the client to quit. + WriteMessage(server_mp, "quit"); + + // 10. Wait on |mp2| (which should eventually fail) and then close it. + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(mp2, MOJO_HANDLE_SIGNAL_READABLE, &state)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp2)); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessChannelsClient, EmbedderTest, + client_mp) { + // 1. Read the first message from |client_mp|. + EXPECT_EQ("hello", ReadMessage(client_mp)); + + // 2. Write a message to |client_mp| (attaching nothing). + WriteMessage(client_mp, "world!"); + + // 4. Read a message from |client_mp|, which should have |mp1| attached. + MojoHandle mp1; + EXPECT_EQ("Bar", ReadMessageWithHandles(client_mp, &mp1, 1)); + + // 5. Create a new message pipe (endpoints |mp2| and |mp3|). + MojoHandle mp2, mp3; + CreateMessagePipe(&mp2, &mp3); + + // 6. Write a message to |mp3|. + WriteMessage(mp3, "baz"); + + // 7. Close |mp3|. + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp3)); + + // 8. Write a message to |mp1|, attaching |mp2|. + WriteMessageWithHandles(mp1, "quux", &mp2, 1); + mp2 = MOJO_HANDLE_INVALID; + + // 9. Read a message from |mp1|. + EXPECT_EQ("FOO", ReadMessage(mp1)); + + EXPECT_EQ("quit", ReadMessage(client_mp)); + + // 10. Wait on |mp1| (which should eventually fail) and then close it. + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &state)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp1)); +} + +TEST_F(EmbedderTest, MultiprocessBaseSharedMemory) { + RUN_CHILD_ON_PIPE(MultiprocessSharedMemoryClient, server_mp) + // 1. Create a base::SharedMemory object and create a mojo shared buffer + // from it. + base::SharedMemoryCreateOptions options; + options.size = 123; + base::SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.Create(options)); + base::SharedMemoryHandle shm_handle = base::SharedMemory::DuplicateHandle( + shared_memory.handle()); + MojoHandle sb1; + ASSERT_EQ(MOJO_RESULT_OK, + CreateSharedBufferWrapper(shm_handle, 123, false, &sb1)); + + // 2. Map |sb1| and write something into it. + char* buffer = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(sb1, 0, 123, reinterpret_cast(&buffer), 0)); + ASSERT_TRUE(buffer); + memcpy(buffer, kHelloWorld, sizeof(kHelloWorld)); + + // 3. Duplicate |sb1| into |sb2| and pass to |server_mp|. + MojoHandle sb2 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoDuplicateBufferHandle(sb1, 0, &sb2)); + EXPECT_NE(MOJO_HANDLE_INVALID, sb2); + WriteMessageWithHandles(server_mp, "hello", &sb2, 1); + + // 4. Read a message from |server_mp|. + EXPECT_EQ("bye", ReadMessage(server_mp)); + + // 5. Expect that the contents of the shared buffer have changed. + EXPECT_EQ(kByeWorld, std::string(buffer)); + + // 6. Map the original base::SharedMemory and expect it contains the + // expected value. + ASSERT_TRUE(shared_memory.Map(123)); + EXPECT_EQ(kByeWorld, + std::string(static_cast(shared_memory.memory()))); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(sb1)); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessSharedMemoryClient, EmbedderTest, + client_mp) { + // 1. Read the first message from |client_mp|, which should have |sb1| which + // should be a shared buffer handle. + MojoHandle sb1; + EXPECT_EQ("hello", ReadMessageWithHandles(client_mp, &sb1, 1)); + + // 2. Map |sb1|. + char* buffer = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(sb1, 0, 123, reinterpret_cast(&buffer), 0)); + ASSERT_TRUE(buffer); + + // 3. Ensure |buffer| contains the values we expect. + EXPECT_EQ(kHelloWorld, std::string(buffer)); + + // 4. Write into |buffer| and send a message back. + memcpy(buffer, kByeWorld, sizeof(kByeWorld)); + WriteMessage(client_mp, "bye"); + + // 5. Extract the shared memory handle and ensure we can map it and read the + // contents. + base::SharedMemoryHandle shm_handle; + ASSERT_EQ(MOJO_RESULT_OK, + PassSharedMemoryHandle(sb1, &shm_handle, nullptr, nullptr)); + base::SharedMemory shared_memory(shm_handle, false); + ASSERT_TRUE(shared_memory.Map(123)); + EXPECT_NE(buffer, shared_memory.memory()); + EXPECT_EQ(kByeWorld, std::string(static_cast(shared_memory.memory()))); + + // 6. Close |sb1|. Should fail because |PassSharedMemoryHandle()| should have + // closed the handle. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(sb1)); +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +TEST_F(EmbedderTest, MultiprocessMachSharedMemory) { + RUN_CHILD_ON_PIPE(MultiprocessSharedMemoryClient, server_mp) + // 1. Create a Mach base::SharedMemory object and create a mojo shared + // buffer from it. + base::SharedMemoryCreateOptions options; + options.size = 123; + base::SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.Create(options)); + base::SharedMemoryHandle shm_handle = base::SharedMemory::DuplicateHandle( + shared_memory.handle()); + MojoHandle sb1; + ASSERT_EQ(MOJO_RESULT_OK, + CreateSharedBufferWrapper(shm_handle, 123, false, &sb1)); + + // 2. Map |sb1| and write something into it. + char* buffer = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(sb1, 0, 123, reinterpret_cast(&buffer), 0)); + ASSERT_TRUE(buffer); + memcpy(buffer, kHelloWorld, sizeof(kHelloWorld)); + + // 3. Duplicate |sb1| into |sb2| and pass to |server_mp|. + MojoHandle sb2 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoDuplicateBufferHandle(sb1, 0, &sb2)); + EXPECT_NE(MOJO_HANDLE_INVALID, sb2); + WriteMessageWithHandles(server_mp, "hello", &sb2, 1); + + // 4. Read a message from |server_mp|. + EXPECT_EQ("bye", ReadMessage(server_mp)); + + // 5. Expect that the contents of the shared buffer have changed. + EXPECT_EQ(kByeWorld, std::string(buffer)); + + // 6. Map the original base::SharedMemory and expect it contains the + // expected value. + ASSERT_TRUE(shared_memory.Map(123)); + EXPECT_EQ(kByeWorld, + std::string(static_cast(shared_memory.memory()))); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(sb1)); + END_CHILD() +} + +enum class HandleType { + POSIX, + MACH, + MACH_NULL, +}; + +const HandleType kTestHandleTypes[] = { + HandleType::MACH, + HandleType::MACH_NULL, + HandleType::POSIX, + HandleType::POSIX, + HandleType::MACH, +}; + +// Test that we can mix file descriptors and mach port handles. +TEST_F(EmbedderTest, MultiprocessMixMachAndFds) { + const size_t kShmSize = 1234; + RUN_CHILD_ON_PIPE(MultiprocessMixMachAndFdsClient, server_mp) + // 1. Create fds or Mach objects and mojo handles from them. + MojoHandle platform_handles[arraysize(kTestHandleTypes)]; + for (size_t i = 0; i < arraysize(kTestHandleTypes); i++) { + const auto type = kTestHandleTypes[i]; + ScopedPlatformHandle scoped_handle; + if (type == HandleType::POSIX) { + // The easiest source of fds is opening /dev/null. + base::File file(base::FilePath("/dev/null"), + base::File::FLAG_OPEN | base::File::FLAG_WRITE); + ASSERT_TRUE(file.IsValid()); + scoped_handle.reset(PlatformHandle(file.TakePlatformFile())); + EXPECT_EQ(PlatformHandle::Type::POSIX, scoped_handle.get().type); + } else if (type == HandleType::MACH_NULL) { + scoped_handle.reset(PlatformHandle( + static_cast(MACH_PORT_NULL))); + EXPECT_EQ(PlatformHandle::Type::MACH, scoped_handle.get().type); + } else { + base::SharedMemoryCreateOptions options; + options.size = kShmSize; + base::SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.Create(options)); + base::SharedMemoryHandle shm_handle = + base::SharedMemory::DuplicateHandle(shared_memory.handle()); + scoped_handle.reset(PlatformHandle(shm_handle.GetMemoryObject())); + EXPECT_EQ(PlatformHandle::Type::MACH, scoped_handle.get().type); + } + ASSERT_EQ(MOJO_RESULT_OK, CreatePlatformHandleWrapper( + std::move(scoped_handle), platform_handles + i)); + } + + // 2. Send all the handles to the child. + WriteMessageWithHandles(server_mp, "hello", platform_handles, + arraysize(kTestHandleTypes)); + + // 3. Read a message from |server_mp|. + EXPECT_EQ("bye", ReadMessage(server_mp)); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessMixMachAndFdsClient, EmbedderTest, + client_mp) { + const int kNumHandles = arraysize(kTestHandleTypes); + MojoHandle platform_handles[kNumHandles]; + + // 1. Read from |client_mp|, which should have a message containing + // |kNumHandles| handles. + EXPECT_EQ("hello", + ReadMessageWithHandles(client_mp, platform_handles, kNumHandles)); + + // 2. Extract each handle, and verify the type. + for (int i = 0; i < kNumHandles; i++) { + const auto type = kTestHandleTypes[i]; + ScopedPlatformHandle scoped_handle; + ASSERT_EQ(MOJO_RESULT_OK, + PassWrappedPlatformHandle(platform_handles[i], &scoped_handle)); + if (type == HandleType::POSIX) { + EXPECT_NE(0, scoped_handle.get().handle); + EXPECT_EQ(PlatformHandle::Type::POSIX, scoped_handle.get().type); + } else if (type == HandleType::MACH_NULL) { + EXPECT_EQ(static_cast(MACH_PORT_NULL), + scoped_handle.get().port); + EXPECT_EQ(PlatformHandle::Type::MACH, scoped_handle.get().type); + } else { + EXPECT_NE(static_cast(MACH_PORT_NULL), + scoped_handle.get().port); + EXPECT_EQ(PlatformHandle::Type::MACH, scoped_handle.get().type); + } + } + + // 3. Say bye! + WriteMessage(client_mp, "bye"); +} + +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +// TODO(vtl): Test immediate write & close. +// TODO(vtl): Test broken-connection cases. + +#endif // !defined(OS_IOS) + +NamedPlatformHandle GenerateChannelName() { +#if defined(OS_POSIX) + base::FilePath temp_dir; + CHECK(base::PathService::Get(base::DIR_TEMP, &temp_dir)); + return NamedPlatformHandle( + temp_dir.AppendASCII(GenerateRandomToken()).value()); +#else + return NamedPlatformHandle(GenerateRandomToken()); +#endif +} + +void CreateClientHandleOnIoThread(const NamedPlatformHandle& named_handle, + ScopedPlatformHandle* output) { + *output = CreateClientHandle(named_handle); +} + +TEST_F(EmbedderTest, ClosePendingPeerConnection) { + NamedPlatformHandle named_handle = GenerateChannelName(); + std::string peer_token = GenerateRandomToken(); + ScopedMessagePipeHandle server_pipe = + ConnectToPeerProcess(CreateServerHandle(named_handle), peer_token); + ClosePeerConnection(peer_token); + EXPECT_EQ(MOJO_RESULT_OK, + Wait(server_pipe.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + base::MessageLoop message_loop; + base::RunLoop run_loop; + ScopedPlatformHandle client_handle; + // Closing the channel involves posting a task to the IO thread to do the + // work. By the time the local message pipe has been observerd as closed, + // that task will have been posted. Therefore, a task to create the client + // connection should be handled after the channel is closed. + GetIOTaskRunner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&CreateClientHandleOnIoThread, named_handle, &client_handle), + run_loop.QuitClosure()); + run_loop.Run(); + EXPECT_FALSE(client_handle.is_valid()); +} + +#if !defined(OS_IOS) + +TEST_F(EmbedderTest, ClosePipeToConnectedPeer) { + set_launch_type(LaunchType::PEER); + auto& controller = StartClient("ClosePipeToConnectedPeerClient"); + MojoHandle server_mp = controller.pipe(); + // 1. Write a message to |server_mp| (attaching nothing). + WriteMessage(server_mp, "hello"); + + // 2. Read a message from |server_mp|. + EXPECT_EQ("world!", ReadMessage(server_mp)); + + controller.ClosePeerConnection(); + + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + EXPECT_EQ(0, controller.WaitForShutdown()); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ClosePipeToConnectedPeerClient, EmbedderTest, + client_mp) { + // 1. Read the first message from |client_mp|. + EXPECT_EQ("hello", ReadMessage(client_mp)); + + // 2. Write a message to |client_mp| (attaching nothing). + WriteMessage(client_mp, "world!"); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +TEST_F(EmbedderTest, ClosePipeToConnectingPeer) { + set_launch_type(LaunchType::PEER); + auto& controller = StartClient("ClosePipeToConnectingPeerClient"); + controller.ClosePeerConnection(); + + MojoHandle server_mp = controller.pipe(); + + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + EXPECT_EQ(0, controller.WaitForShutdown()); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ClosePipeToConnectingPeerClient, EmbedderTest, + client_mp) { + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +#endif // !defined(OS_IOS) + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/entrypoints.cc b/mojo/edk/embedder/entrypoints.cc new file mode 100644 index 0000000000000000000000000000000000000000..908136855068fe880509df9812568bc81cbf63b4 --- /dev/null +++ b/mojo/edk/embedder/entrypoints.cc @@ -0,0 +1,283 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/entrypoints.h" + +#include + +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/core.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/platform_handle.h" + +using mojo::edk::internal::g_core; + +// Definitions of the system functions. +extern "C" { + +MojoTimeTicks MojoGetTimeTicksNowImpl() { + return g_core->GetTimeTicksNow(); +} + +MojoResult MojoCloseImpl(MojoHandle handle) { + return g_core->Close(handle); +} + +MojoResult MojoQueryHandleSignalsStateImpl( + MojoHandle handle, + MojoHandleSignalsState* signals_state) { + return g_core->QueryHandleSignalsState(handle, signals_state); +} + +MojoResult MojoCreateWatcherImpl(MojoWatcherCallback callback, + MojoHandle* watcher_handle) { + return g_core->CreateWatcher(callback, watcher_handle); +} + +MojoResult MojoArmWatcherImpl(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + return g_core->ArmWatcher(watcher_handle, num_ready_contexts, ready_contexts, + ready_results, ready_signals_states); +} + +MojoResult MojoWatchImpl(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context) { + return g_core->Watch(watcher_handle, handle, signals, context); +} + +MojoResult MojoCancelWatchImpl(MojoHandle watcher_handle, uintptr_t context) { + return g_core->CancelWatch(watcher_handle, context); +} + +MojoResult MojoAllocMessageImpl(uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoAllocMessageFlags flags, + MojoMessageHandle* message) { + return g_core->AllocMessage(num_bytes, handles, num_handles, flags, message); +} + +MojoResult MojoFreeMessageImpl(MojoMessageHandle message) { + return g_core->FreeMessage(message); +} + +MojoResult MojoGetMessageBufferImpl(MojoMessageHandle message, void** buffer) { + return g_core->GetMessageBuffer(message, buffer); +} + +MojoResult MojoCreateMessagePipeImpl( + const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1) { + return g_core->CreateMessagePipe(options, message_pipe_handle0, + message_pipe_handle1); +} + +MojoResult MojoWriteMessageImpl(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + return g_core->WriteMessage(message_pipe_handle, bytes, num_bytes, handles, + num_handles, flags); +} + +MojoResult MojoWriteMessageNewImpl(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags flags) { + return g_core->WriteMessageNew(message_pipe_handle, message, flags); +} + +MojoResult MojoReadMessageImpl(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + return g_core->ReadMessage( + message_pipe_handle, bytes, num_bytes, handles, num_handles, flags); +} + +MojoResult MojoReadMessageNewImpl(MojoHandle message_pipe_handle, + MojoMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + return g_core->ReadMessageNew( + message_pipe_handle, message, num_bytes, handles, num_handles, flags); +} + +MojoResult MojoFuseMessagePipesImpl(MojoHandle handle0, MojoHandle handle1) { + return g_core->FuseMessagePipes(handle0, handle1); +} + +MojoResult MojoCreateDataPipeImpl(const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle) { + return g_core->CreateDataPipe(options, data_pipe_producer_handle, + data_pipe_consumer_handle); +} + +MojoResult MojoWriteDataImpl(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_elements, + MojoWriteDataFlags flags) { + return g_core->WriteData(data_pipe_producer_handle, elements, num_elements, + flags); +} + +MojoResult MojoBeginWriteDataImpl(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_elements, + MojoWriteDataFlags flags) { + return g_core->BeginWriteData(data_pipe_producer_handle, buffer, + buffer_num_elements, flags); +} + +MojoResult MojoEndWriteDataImpl(MojoHandle data_pipe_producer_handle, + uint32_t num_elements_written) { + return g_core->EndWriteData(data_pipe_producer_handle, num_elements_written); +} + +MojoResult MojoReadDataImpl(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_elements, + MojoReadDataFlags flags) { + return g_core->ReadData(data_pipe_consumer_handle, elements, num_elements, + flags); +} + +MojoResult MojoBeginReadDataImpl(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_elements, + MojoReadDataFlags flags) { + return g_core->BeginReadData(data_pipe_consumer_handle, buffer, + buffer_num_elements, flags); +} + +MojoResult MojoEndReadDataImpl(MojoHandle data_pipe_consumer_handle, + uint32_t num_elements_read) { + return g_core->EndReadData(data_pipe_consumer_handle, num_elements_read); +} + +MojoResult MojoCreateSharedBufferImpl( + const struct MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle) { + return g_core->CreateSharedBuffer(options, num_bytes, shared_buffer_handle); +} + +MojoResult MojoDuplicateBufferHandleImpl( + MojoHandle buffer_handle, + const struct MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle) { + return g_core->DuplicateBufferHandle(buffer_handle, options, + new_buffer_handle); +} + +MojoResult MojoMapBufferImpl(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags) { + return g_core->MapBuffer(buffer_handle, offset, num_bytes, buffer, flags); +} + +MojoResult MojoUnmapBufferImpl(void* buffer) { + return g_core->UnmapBuffer(buffer); +} + +MojoResult MojoWrapPlatformHandleImpl(const MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle) { + return g_core->WrapPlatformHandle(platform_handle, mojo_handle); +} + +MojoResult MojoUnwrapPlatformHandleImpl(MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle) { + return g_core->UnwrapPlatformHandle(mojo_handle, platform_handle); +} + +MojoResult MojoWrapPlatformSharedBufferHandleImpl( + const MojoPlatformHandle* platform_handle, + size_t num_bytes, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle) { + return g_core->WrapPlatformSharedBufferHandle(platform_handle, num_bytes, + flags, mojo_handle); +} + +MojoResult MojoUnwrapPlatformSharedBufferHandleImpl( + MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle, + size_t* num_bytes, + MojoPlatformSharedBufferHandleFlags* flags) { + return g_core->UnwrapPlatformSharedBufferHandle(mojo_handle, platform_handle, + num_bytes, flags); +} + +MojoResult MojoNotifyBadMessageImpl(MojoMessageHandle message, + const char* error, + size_t error_num_bytes) { + return g_core->NotifyBadMessage(message, error, error_num_bytes); +} + +MojoResult MojoGetPropertyImpl(MojoPropertyType type, void* value) { + return g_core->GetProperty(type, value); +} + +} // extern "C" + +namespace mojo { +namespace edk { + +MojoSystemThunks MakeSystemThunks() { + MojoSystemThunks system_thunks = {sizeof(MojoSystemThunks), + MojoGetTimeTicksNowImpl, + MojoCloseImpl, + MojoQueryHandleSignalsStateImpl, + MojoCreateMessagePipeImpl, + MojoWriteMessageImpl, + MojoReadMessageImpl, + MojoCreateDataPipeImpl, + MojoWriteDataImpl, + MojoBeginWriteDataImpl, + MojoEndWriteDataImpl, + MojoReadDataImpl, + MojoBeginReadDataImpl, + MojoEndReadDataImpl, + MojoCreateSharedBufferImpl, + MojoDuplicateBufferHandleImpl, + MojoMapBufferImpl, + MojoUnmapBufferImpl, + MojoCreateWatcherImpl, + MojoWatchImpl, + MojoCancelWatchImpl, + MojoArmWatcherImpl, + MojoFuseMessagePipesImpl, + MojoWriteMessageNewImpl, + MojoReadMessageNewImpl, + MojoAllocMessageImpl, + MojoFreeMessageImpl, + MojoGetMessageBufferImpl, + MojoWrapPlatformHandleImpl, + MojoUnwrapPlatformHandleImpl, + MojoWrapPlatformSharedBufferHandleImpl, + MojoUnwrapPlatformSharedBufferHandleImpl, + MojoNotifyBadMessageImpl, + MojoGetPropertyImpl}; + return system_thunks; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/entrypoints.h b/mojo/edk/embedder/entrypoints.h new file mode 100644 index 0000000000000000000000000000000000000000..8e448c156f71e1f211fda0308c6732bea428be5a --- /dev/null +++ b/mojo/edk/embedder/entrypoints.h @@ -0,0 +1,22 @@ +// Copyright 2016 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 MOJO_EDK_EMBEDDER_ENTRYPOINTS_H_ +#define MOJO_EDK_EMBEDDER_ENTRYPOINTS_H_ + +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/thunks.h" + +namespace mojo { +namespace edk { + +// Creates a MojoSystemThunks struct populated with the EDK's implementation of +// each function. This may be used by embedders to populate thunks for +// application loading. +MOJO_SYSTEM_IMPL_EXPORT MojoSystemThunks MakeSystemThunks(); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_ENTRYPOINTS_H_ diff --git a/mojo/edk/embedder/named_platform_channel_pair.h b/mojo/edk/embedder/named_platform_channel_pair.h new file mode 100644 index 0000000000000000000000000000000000000000..5a83ae3b85389fe4dfe96aa032eb1e32a485bd48 --- /dev/null +++ b/mojo/edk/embedder/named_platform_channel_pair.h @@ -0,0 +1,73 @@ +// Copyright 2016 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 MOJO_EDK_EMBEDDER_NAMED_PLATFORM_CHANNEL_PAIR_H_ +#define MOJO_EDK_EMBEDDER_NAMED_PLATFORM_CHANNEL_PAIR_H_ + +#include + +#include "base/macros.h" +#include "base/strings/string16.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace base { +class CommandLine; +} + +namespace mojo { +namespace edk { + +// This is used to create a named bidirectional pipe to connect new child +// processes. The resulting server handle should be passed to the EDK, and the +// child end passed as a pipe name on the command line to the child process. The +// child process can then retrieve the pipe name from the command line and +// resolve it into a client handle. +class MOJO_SYSTEM_IMPL_EXPORT NamedPlatformChannelPair { + public: + struct Options { +#if defined(OS_WIN) + // If non-empty, a security descriptor to use when creating the pipe. If + // empty, a default security descriptor will be used. See + // kDefaultSecurityDescriptor in named_platform_handle_utils_win.cc. + base::string16 security_descriptor; +#endif + }; + + NamedPlatformChannelPair(const Options& options = {}); + ~NamedPlatformChannelPair(); + + // Note: It is NOT acceptable to use this handle as a generic pipe channel. It + // MUST be passed to PendingProcessConnection::Connect() only. + ScopedPlatformHandle PassServerHandle(); + + // To be called in the child process, after the parent process called + // |PrepareToPassClientHandleToChildProcess()| and launched the child (using + // the provided data), to create a client handle connected to the server + // handle (in the parent process). + static ScopedPlatformHandle PassClientHandleFromParentProcess( + const base::CommandLine& command_line); + + // Prepares to pass the client channel to a new child process, to be launched + // using |LaunchProcess()| (from base/launch.h). Modifies |*command_line| and + // |*handle_passing_info| as needed. + // Note: For Windows, this method only works on Vista and later. + void PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line) const; + + const NamedPlatformHandle& handle() const { return pipe_handle_; } + + private: + NamedPlatformHandle pipe_handle_; + ScopedPlatformHandle server_handle_; + + DISALLOW_COPY_AND_ASSIGN(NamedPlatformChannelPair); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_NAMED_PLATFORM_CHANNEL_PAIR_H_ diff --git a/mojo/edk/embedder/named_platform_channel_pair_win.cc b/mojo/edk/embedder/named_platform_channel_pair_win.cc new file mode 100644 index 0000000000000000000000000000000000000000..96589ff5cfb1fbc5b0f89bd8147b6ea0a58c0d40 --- /dev/null +++ b/mojo/edk/embedder/named_platform_channel_pair_win.cc @@ -0,0 +1,89 @@ +// Copyright 2016 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. + +#include "mojo/edk/embedder/named_platform_channel_pair.h" + +#include + +#include +#include +#include + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/windows_version.h" +#include "mojo/edk/embedder/named_platform_handle_utils.h" +#include "mojo/edk/embedder/platform_handle.h" + +namespace mojo { +namespace edk { + +namespace { + +const char kMojoNamedPlatformChannelPipeSwitch[] = + "mojo-named-platform-channel-pipe"; + +std::wstring GeneratePipeName() { + return base::StringPrintf(L"%u.%u.%I64u", GetCurrentProcessId(), + GetCurrentThreadId(), base::RandUint64()); +} + +} // namespace + +NamedPlatformChannelPair::NamedPlatformChannelPair( + const NamedPlatformChannelPair::Options& options) + : pipe_handle_(GeneratePipeName()) { + CreateServerHandleOptions server_handle_options; + server_handle_options.security_descriptor = options.security_descriptor; + server_handle_options.enforce_uniqueness = true; + server_handle_ = CreateServerHandle(pipe_handle_, server_handle_options); + PCHECK(server_handle_.is_valid()); +} + +NamedPlatformChannelPair::~NamedPlatformChannelPair() {} + +ScopedPlatformHandle NamedPlatformChannelPair::PassServerHandle() { + return std::move(server_handle_); +} + +// static +ScopedPlatformHandle +NamedPlatformChannelPair::PassClientHandleFromParentProcess( + const base::CommandLine& command_line) { + // In order to support passing the pipe name on the command line, the pipe + // handle is lazily created from the pipe name when requested. + NamedPlatformHandle handle( + command_line.GetSwitchValueNative(kMojoNamedPlatformChannelPipeSwitch)); + + if (!handle.is_valid()) + return ScopedPlatformHandle(); + + return CreateClientHandle(handle); +} + +void NamedPlatformChannelPair::PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line) const { + DCHECK(command_line); + + // Log a warning if the command line already has the switch, but "clobber" it + // anyway, since it's reasonably likely that all the switches were just copied + // from the parent. + LOG_IF(WARNING, + command_line->HasSwitch(kMojoNamedPlatformChannelPipeSwitch)) + << "Child command line already has switch --" + << kMojoNamedPlatformChannelPipeSwitch << "=" + << command_line->GetSwitchValueNative( + kMojoNamedPlatformChannelPipeSwitch); + // (Any existing switch won't actually be removed from the command line, but + // the last one appended takes precedence.) + command_line->AppendSwitchNative(kMojoNamedPlatformChannelPipeSwitch, + pipe_handle_.name); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/named_platform_handle.h b/mojo/edk/embedder/named_platform_handle.h new file mode 100644 index 0000000000000000000000000000000000000000..15ca6565d51580a7e7f2fd58a2c7fc20e0a38256 --- /dev/null +++ b/mojo/edk/embedder/named_platform_handle.h @@ -0,0 +1,51 @@ +// Copyright 2016 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 MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_H_ +#define MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_H_ + +#include + +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +#if defined(OS_POSIX) +struct MOJO_SYSTEM_IMPL_EXPORT NamedPlatformHandle { + NamedPlatformHandle() {} + explicit NamedPlatformHandle(const base::StringPiece& name) + : name(name.as_string()) {} + + bool is_valid() const { return !name.empty(); } + + std::string name; +}; +#elif defined(OS_WIN) +struct MOJO_SYSTEM_IMPL_EXPORT NamedPlatformHandle { + NamedPlatformHandle() {} + explicit NamedPlatformHandle(const base::StringPiece& name) + : name(base::UTF8ToUTF16(name)) {} + + explicit NamedPlatformHandle(const base::StringPiece16& name) + : name(name.as_string()) {} + + bool is_valid() const { return !name.empty(); } + + base::string16 pipe_name() const { return L"\\\\.\\pipe\\mojo." + name; } + + base::string16 name; +}; +#else +#error "Platform not yet supported." +#endif + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_H_ diff --git a/mojo/edk/embedder/named_platform_handle_utils.h b/mojo/edk/embedder/named_platform_handle_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..b767ea0975c27fa5187a91b11d5689db3517cc00 --- /dev/null +++ b/mojo/edk/embedder/named_platform_handle_utils.h @@ -0,0 +1,56 @@ +// Copyright 2016 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 MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_UTILS_H_ +#define MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_UTILS_H_ + +#include "build/build_config.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +#if defined(OS_WIN) +#include "base/strings/string16.h" +#endif + +namespace mojo { +namespace edk { + +struct NamedPlatformHandle; + +#if defined(OS_POSIX) + +// The maximum length of the name of a unix domain socket. The standard size on +// linux is 108, mac is 104. To maintain consistency across platforms we +// standardize on the smaller value. +const size_t kMaxSocketNameLength = 104; + +#endif + +struct CreateServerHandleOptions { +#if defined(OS_WIN) + // If true, creating a server handle will fail if another pipe with the same + // name exists. + bool enforce_uniqueness = true; + + // If non-empty, a security descriptor to use when creating the pipe. If + // empty, a default security descriptor will be used. See + // kDefaultSecurityDescriptor in named_platform_handle_utils_win.cc. + base::string16 security_descriptor; +#endif +}; + +// Creates a client platform handle from |handle|. This may block until |handle| +// is ready to receive connections. +MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle +CreateClientHandle(const NamedPlatformHandle& handle); + +// Creates a server platform handle from |handle|. +MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle +CreateServerHandle(const NamedPlatformHandle& handle, + const CreateServerHandleOptions& options = {}); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_UTILS_H_ diff --git a/mojo/edk/embedder/named_platform_handle_utils_posix.cc b/mojo/edk/embedder/named_platform_handle_utils_posix.cc new file mode 100644 index 0000000000000000000000000000000000000000..056f4d64e5d4c43611434374279513430a2c75f5 --- /dev/null +++ b/mojo/edk/embedder/named_platform_handle_utils_posix.cc @@ -0,0 +1,141 @@ +// Copyright 2016 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. + +#include "mojo/edk/embedder/named_platform_handle_utils.h" + +#include +#include +#include +#include + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "mojo/edk/embedder/named_platform_handle.h" + +namespace mojo { +namespace edk { +namespace { + +// This function fills in |unix_addr| with the appropriate data for the socket, +// and sets |unix_addr_len| to the length of the data therein. +// Returns true on success, or false on failure (typically because |handle.name| +// violated the naming rules). +bool MakeUnixAddr(const NamedPlatformHandle& handle, + struct sockaddr_un* unix_addr, + size_t* unix_addr_len) { + DCHECK(unix_addr); + DCHECK(unix_addr_len); + DCHECK(handle.is_valid()); + + // We reject handle.name.length() == kMaxSocketNameLength to make room for the + // NUL terminator at the end of the string. + if (handle.name.length() >= kMaxSocketNameLength) { + LOG(ERROR) << "Socket name too long: " << handle.name; + return false; + } + + // Create unix_addr structure. + memset(unix_addr, 0, sizeof(struct sockaddr_un)); + unix_addr->sun_family = AF_UNIX; + strncpy(unix_addr->sun_path, handle.name.c_str(), kMaxSocketNameLength); + *unix_addr_len = + offsetof(struct sockaddr_un, sun_path) + handle.name.length(); + return true; +} + +// This function creates a unix domain socket, and set it as non-blocking. +// If successful, this returns a ScopedPlatformHandle containing the socket. +// Otherwise, this returns an invalid ScopedPlatformHandle. +ScopedPlatformHandle CreateUnixDomainSocket(bool needs_connection) { + // Create the unix domain socket. + PlatformHandle socket_handle(socket(AF_UNIX, SOCK_STREAM, 0)); + socket_handle.needs_connection = needs_connection; + ScopedPlatformHandle handle(socket_handle); + if (!handle.is_valid()) { + PLOG(ERROR) << "Failed to create AF_UNIX socket."; + return ScopedPlatformHandle(); + } + + // Now set it as non-blocking. + if (!base::SetNonBlocking(handle.get().handle)) { + PLOG(ERROR) << "base::SetNonBlocking() failed " << handle.get().handle; + return ScopedPlatformHandle(); + } + return handle; +} + +} // namespace + +ScopedPlatformHandle CreateClientHandle( + const NamedPlatformHandle& named_handle) { + if (!named_handle.is_valid()) + return ScopedPlatformHandle(); + + struct sockaddr_un unix_addr; + size_t unix_addr_len; + if (!MakeUnixAddr(named_handle, &unix_addr, &unix_addr_len)) + return ScopedPlatformHandle(); + + ScopedPlatformHandle handle = CreateUnixDomainSocket(false); + if (!handle.is_valid()) + return ScopedPlatformHandle(); + + if (HANDLE_EINTR(connect(handle.get().handle, + reinterpret_cast(&unix_addr), + unix_addr_len)) < 0) { + PLOG(ERROR) << "connect " << named_handle.name; + return ScopedPlatformHandle(); + } + + return handle; +} + +ScopedPlatformHandle CreateServerHandle( + const NamedPlatformHandle& named_handle, + const CreateServerHandleOptions& options) { + if (!named_handle.is_valid()) + return ScopedPlatformHandle(); + + // Make sure the path we need exists. + base::FilePath socket_dir = base::FilePath(named_handle.name).DirName(); + if (!base::CreateDirectory(socket_dir)) { + LOG(ERROR) << "Couldn't create directory: " << socket_dir.value(); + return ScopedPlatformHandle(); + } + + // Delete any old FS instances. + if (unlink(named_handle.name.c_str()) < 0 && errno != ENOENT) { + PLOG(ERROR) << "unlink " << named_handle.name; + return ScopedPlatformHandle(); + } + + struct sockaddr_un unix_addr; + size_t unix_addr_len; + if (!MakeUnixAddr(named_handle, &unix_addr, &unix_addr_len)) + return ScopedPlatformHandle(); + + ScopedPlatformHandle handle = CreateUnixDomainSocket(true); + if (!handle.is_valid()) + return ScopedPlatformHandle(); + + // Bind the socket. + if (bind(handle.get().handle, reinterpret_cast(&unix_addr), + unix_addr_len) < 0) { + PLOG(ERROR) << "bind " << named_handle.name; + return ScopedPlatformHandle(); + } + + // Start listening on the socket. + if (listen(handle.get().handle, SOMAXCONN) < 0) { + PLOG(ERROR) << "listen " << named_handle.name; + unlink(named_handle.name.c_str()); + return ScopedPlatformHandle(); + } + return handle; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/named_platform_handle_utils_win.cc b/mojo/edk/embedder/named_platform_handle_utils_win.cc new file mode 100644 index 0000000000000000000000000000000000000000..a145847fd04e592d11baa4c510a8436e45abe05e --- /dev/null +++ b/mojo/edk/embedder/named_platform_handle_utils_win.cc @@ -0,0 +1,95 @@ +// Copyright 2016 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. + +#include "mojo/edk/embedder/named_platform_handle_utils.h" + +#include +#include + +#include + +#include "base/logging.h" +#include "base/win/windows_version.h" +#include "mojo/edk/embedder/named_platform_handle.h" + +namespace mojo { +namespace edk { +namespace { + +// A DACL to grant: +// GA = Generic All +// access to: +// SY = LOCAL_SYSTEM +// BA = BUILTIN_ADMINISTRATORS +// OW = OWNER_RIGHTS +constexpr base::char16 kDefaultSecurityDescriptor[] = + L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;OW)"; + +} // namespace + +ScopedPlatformHandle CreateClientHandle( + const NamedPlatformHandle& named_handle) { + if (!named_handle.is_valid()) + return ScopedPlatformHandle(); + + base::string16 pipe_name = named_handle.pipe_name(); + + // Note: This may block. + if (!WaitNamedPipeW(pipe_name.c_str(), NMPWAIT_USE_DEFAULT_WAIT)) + return ScopedPlatformHandle(); + + const DWORD kDesiredAccess = GENERIC_READ | GENERIC_WRITE; + // The SECURITY_ANONYMOUS flag means that the server side cannot impersonate + // the client. + const DWORD kFlags = + SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS | FILE_FLAG_OVERLAPPED; + ScopedPlatformHandle handle( + PlatformHandle(CreateFileW(pipe_name.c_str(), kDesiredAccess, + 0, // No sharing. + nullptr, OPEN_EXISTING, kFlags, + nullptr))); // No template file. + // The server may have stopped accepting a connection between the + // WaitNamedPipe() and CreateFile(). If this occurs, an invalid handle is + // returned. + DPLOG_IF(ERROR, !handle.is_valid()) + << "Named pipe " << named_handle.pipe_name() + << " could not be opened after WaitNamedPipe succeeded"; + return handle; +} + +ScopedPlatformHandle CreateServerHandle( + const NamedPlatformHandle& named_handle, + const CreateServerHandleOptions& options) { + if (!named_handle.is_valid()) + return ScopedPlatformHandle(); + + PSECURITY_DESCRIPTOR security_desc = nullptr; + ULONG security_desc_len = 0; + PCHECK(ConvertStringSecurityDescriptorToSecurityDescriptor( + options.security_descriptor.empty() ? kDefaultSecurityDescriptor + : options.security_descriptor.c_str(), + SDDL_REVISION_1, &security_desc, &security_desc_len)); + std::unique_ptr p(security_desc, ::LocalFree); + SECURITY_ATTRIBUTES security_attributes = {sizeof(SECURITY_ATTRIBUTES), + security_desc, FALSE}; + + const DWORD kOpenMode = options.enforce_uniqueness + ? PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | + FILE_FLAG_FIRST_PIPE_INSTANCE + : PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED; + const DWORD kPipeMode = + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS; + PlatformHandle handle( + CreateNamedPipeW(named_handle.pipe_name().c_str(), kOpenMode, kPipeMode, + options.enforce_uniqueness ? 1 : 255, // Max instances. + 4096, // Out buffer size. + 4096, // In buffer size. + 5000, // Timeout in milliseconds. + &security_attributes)); + handle.needs_connection = true; + return ScopedPlatformHandle(handle); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/pending_process_connection.cc b/mojo/edk/embedder/pending_process_connection.cc new file mode 100644 index 0000000000000000000000000000000000000000..d6be76e81f3910c66ecb5434abf703c4e85092c0 --- /dev/null +++ b/mojo/edk/embedder/pending_process_connection.cc @@ -0,0 +1,50 @@ +// 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. + +#include "mojo/edk/embedder/pending_process_connection.h" + +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/core.h" + +namespace mojo { +namespace edk { + +PendingProcessConnection::PendingProcessConnection() + : process_token_(GenerateRandomToken()) { + DCHECK(internal::g_core); +} + +PendingProcessConnection::~PendingProcessConnection() { + if (has_message_pipes_ && !connected_) { + DCHECK(internal::g_core); + internal::g_core->ChildLaunchFailed(process_token_); + } +} + +ScopedMessagePipeHandle PendingProcessConnection::CreateMessagePipe( + std::string* token) { + has_message_pipes_ = true; + DCHECK(internal::g_core); + *token = GenerateRandomToken(); + return internal::g_core->CreateParentMessagePipe(*token, process_token_); +} + +void PendingProcessConnection::Connect( + base::ProcessHandle process, + ConnectionParams connection_params, + const ProcessErrorCallback& error_callback) { + // It's now safe to avoid cleanup in the destructor, as the lifetime of any + // associated resources is effectively bound to the |channel| passed to + // AddChild() below. + DCHECK(!connected_); + connected_ = true; + + DCHECK(internal::g_core); + internal::g_core->AddChild(process, std::move(connection_params), + process_token_, error_callback); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/pending_process_connection.h b/mojo/edk/embedder/pending_process_connection.h new file mode 100644 index 0000000000000000000000000000000000000000..ca18227696905ad16c67993268f428e2e1e01d93 --- /dev/null +++ b/mojo/edk/embedder/pending_process_connection.h @@ -0,0 +1,124 @@ +// 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. + +#ifndef MOJO_EDK_EMBEDDER_PENDING_PROCESS_CONNECTION_H_ +#define MOJO_EDK_EMBEDDER_PENDING_PROCESS_CONNECTION_H_ + +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/process/process_handle.h" +#include "mojo/edk/embedder/connection_params.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace mojo { +namespace edk { + +using ProcessErrorCallback = base::Callback; + +// Represents a potential connection to an external process. Use this object +// to make other processes reachable from this one via Mojo IPC. Typical usage +// might look something like: +// +// PendingProcessConnection connection; +// +// std::string pipe_token; +// ScopedMessagePipeHandle pipe = connection.CreateMessagePipe(&pipe_token); +// +// // New pipes to the process are fully functional and can be used right +// // away, even if the process doesn't exist yet. +// GoDoSomethingInteresting(std::move(pipe)); +// +// ScopedPlatformChannelPair channel; +// +// // Give the pipe token to the child process via command-line. +// child_command_line.AppendSwitchASCII("yer-pipe", pipe_token); +// +// // Magic child process launcher which gives one end of the pipe to the +// // new process. +// LaunchProcess(child_command_line, channel.PassClientHandle()); +// +// // Some time later... +// connection.Connect(new_process, channel.PassServerHandle()); +// +// If at any point during the above process, |connection| is destroyed before +// Connect() can be called, |pipe| will imminently behave as if its peer has +// been closed. +// +// Otherwise, if the remote process in this example eventually calls: +// +// mojo::edk::SetParentPipeHandle(std::move(client_channel_handle)); +// +// std::string token = command_line.GetSwitchValueASCII("yer-pipe"); +// ScopedMessagePipeHandle pipe = mojo::edk::CreateChildMessagePipe(token); +// +// it will be connected to this process, and its |pipe| will be connected to +// this process's |pipe|. +// +// If the remote process exits or otherwise closes its client channel handle +// before calling CreateChildMessagePipe for a given message pipe token, +// this process's end of the corresponding message pipe will imminently behave +// as if its peer has been closed. +// +class MOJO_SYSTEM_IMPL_EXPORT PendingProcessConnection { + public: + PendingProcessConnection(); + ~PendingProcessConnection(); + + // Creates a message pipe associated with a new globally unique string value + // which will be placed in |*token|. + // + // The other end of the new pipe is obtainable in the remote process (or in + // this process, to facilitate "single-process mode" in some applications) by + // passing the new |*token| value to mojo::edk::CreateChildMessagePipe. It's + // the caller's responsibility to communicate the value of |*token| to the + // remote process by any means available, e.g. a command-line argument on + // process launch, or some other out-of-band communication channel for an + // existing process. + // + // NOTES: This may be called any number of times to create multiple message + // pipes to the same remote process. This call ALWAYS succeeds, returning + // a valid message pipe handle and populating |*token| with a new unique + // string value. + ScopedMessagePipeHandle CreateMessagePipe(std::string* token); + + // Connects to the process. This must be called at most once, with the process + // handle in |process|. + // + // |connection_param| contains the platform handle of an OS pipe which can be + // used to communicate with the connected process. The other end of that pipe + // must ultimately be passed to mojo::edk::SetParentPipeHandle in the remote + // process, and getting that end of the pipe into the other process is the + // embedder's responsibility. + // + // If this method is not called by the time the PendingProcessConnection is + // destroyed, it's assumed that the process is unavailable (e.g. process + // launch failed or the process has otherwise been terminated early), and + // any associated resources, such as remote endpoints of messages pipes + // created by CreateMessagePipe above) will be cleaned up at that time. + void Connect( + base::ProcessHandle process, + ConnectionParams connection_params, + const ProcessErrorCallback& error_callback = ProcessErrorCallback()); + + private: + // A GUID representing a potential new process to be connected to this one. + const std::string process_token_; + + // Indicates whether this object has been used to create new message pipes. + bool has_message_pipes_ = false; + + // Indicates whether Connect() has been called yet. + bool connected_ = false; + + DISALLOW_COPY_AND_ASSIGN(PendingProcessConnection); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PENDING_PROCESS_CONNECTION_H_ diff --git a/mojo/edk/embedder/platform_channel_pair.cc b/mojo/edk/embedder/platform_channel_pair.cc new file mode 100644 index 0000000000000000000000000000000000000000..ee1905abee8f636c4ba02942f9c3faffcd571e32 --- /dev/null +++ b/mojo/edk/embedder/platform_channel_pair.cc @@ -0,0 +1,34 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/platform_channel_pair.h" + +#include + +#include "base/logging.h" + +namespace mojo { +namespace edk { + +const char PlatformChannelPair::kMojoPlatformChannelHandleSwitch[] = + "mojo-platform-channel-handle"; + +PlatformChannelPair::~PlatformChannelPair() { +} + +ScopedPlatformHandle PlatformChannelPair::PassServerHandle() { + return std::move(server_handle_); +} + +ScopedPlatformHandle PlatformChannelPair::PassClientHandle() { + return std::move(client_handle_); +} + +void PlatformChannelPair::ChildProcessLaunched() { + DCHECK(client_handle_.is_valid()); + client_handle_.reset(); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_channel_pair.h b/mojo/edk/embedder/platform_channel_pair.h new file mode 100644 index 0000000000000000000000000000000000000000..9c93f760948725770bd1e410e75991691b2dba50 --- /dev/null +++ b/mojo/edk/embedder/platform_channel_pair.h @@ -0,0 +1,106 @@ +// Copyright 2014 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 MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_ + +#include + +#include "base/macros.h" +#include "base/process/launch.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace base { +class CommandLine; +} + +namespace mojo { +namespace edk { + +// It would be nice to refactor base/process/launch.h to have a more platform- +// independent way of representing handles that are passed to child processes. +#if defined(OS_WIN) +using HandlePassingInformation = base::HandlesToInheritVector; +#elif defined(OS_POSIX) +using HandlePassingInformation = base::FileHandleMappingVector; +#else +#error "Unsupported." +#endif + +// This is used to create a pair of |PlatformHandle|s that are connected by a +// suitable (platform-specific) bidirectional "pipe" (e.g., socket on POSIX, +// named pipe on Windows). The resulting handles can then be used in the same +// process (e.g., in tests) or between processes. (The "server" handle is the +// one that will be used in the process that created the pair, whereas the +// "client" handle is the one that will be used in a different process.) +// +// This class provides facilities for passing the client handle to a child +// process. The parent should call |PrepareToPassClientHandlelToChildProcess()| +// to get the data needed to do this, spawn the child using that data, and then +// call |ChildProcessLaunched()|. Note that on Windows this facility (will) only +// work on Vista and later (TODO(vtl)). +// +// Note: |PlatformChannelPair()|, |PassClientHandleFromParentProcess()| and +// |PrepareToPassClientHandleToChildProcess()| have platform-specific +// implementations. +// +// Note: On POSIX platforms, to write to the "pipe", use +// |PlatformChannel{Write,Writev}()| (from platform_channel_utils_posix.h) +// instead of |write()|, |writev()|, etc. Otherwise, you have to worry about +// platform differences in suppressing |SIGPIPE|. +class MOJO_SYSTEM_IMPL_EXPORT PlatformChannelPair { + public: + static const char kMojoPlatformChannelHandleSwitch[]; + + // If |client_is_blocking| is true, then the client handle only supports + // blocking reads and writes. The default is nonblocking. + PlatformChannelPair(bool client_is_blocking = false); + ~PlatformChannelPair(); + + ScopedPlatformHandle PassServerHandle(); + + // For in-process use (e.g., in tests or to pass over another channel). + ScopedPlatformHandle PassClientHandle(); + + // To be called in the child process, after the parent process called + // |PrepareToPassClientHandleToChildProcess()| and launched the child (using + // the provided data), to create a client handle connected to the server + // handle (in the parent process). + // TODO(jcivelli): remove the command_line param. http://crbug.com/670106 + static ScopedPlatformHandle PassClientHandleFromParentProcess( + const base::CommandLine& command_line); + + // Like above, but gets the handle from the passed in string. + static ScopedPlatformHandle PassClientHandleFromParentProcessFromString( + const std::string& value); + + // Prepares to pass the client channel to a new child process, to be launched + // using |LaunchProcess()| (from base/launch.h). Modifies |*command_line| and + // |*handle_passing_info| as needed. + // Note: For Windows, this method only works on Vista and later. + void PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line, + HandlePassingInformation* handle_passing_info) const; + + // Like above, but returns a string instead of changing the command line. + std::string PrepareToPassClientHandleToChildProcessAsString( + HandlePassingInformation* handle_passing_info) const; + + // To be called once the child process has been successfully launched, to do + // any cleanup necessary. + void ChildProcessLaunched(); + + private: + ScopedPlatformHandle server_handle_; + ScopedPlatformHandle client_handle_; + + DISALLOW_COPY_AND_ASSIGN(PlatformChannelPair); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_ diff --git a/mojo/edk/embedder/platform_channel_pair_posix.cc b/mojo/edk/embedder/platform_channel_pair_posix.cc new file mode 100644 index 0000000000000000000000000000000000000000..fe9f8f5cc7961fd25e7e2fdbb7dc720d20df9016 --- /dev/null +++ b/mojo/edk/embedder/platform_channel_pair_posix.cc @@ -0,0 +1,172 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/platform_channel_pair.h" + +#include +#include +#include +#include +#include + +#include + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/posix/global_descriptors.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/platform_handle.h" + +#if !defined(OS_NACL_SFI) +#include +#else +#include "native_client/src/public/imc_syscalls.h" +#endif + +#if !defined(SO_PEEK_OFF) +#define SO_PEEK_OFF 42 +#endif + +namespace mojo { +namespace edk { + +namespace { + +#if defined(OS_ANDROID) || defined(__ANDROID__) +enum { + // Leave room for any other descriptors defined in content for example. + // TODO(jcivelli): consider changing base::GlobalDescriptors to generate a + // key when setting the file descriptor (http://crbug.com/676442). + kAndroidClientHandleDescriptor = + base::GlobalDescriptors::kBaseDescriptor + 10000, +}; +#else +bool IsTargetDescriptorUsed( + const base::FileHandleMappingVector& file_handle_mapping, + int target_fd) { + for (size_t i = 0; i < file_handle_mapping.size(); i++) { + if (file_handle_mapping[i].second == target_fd) + return true; + } + return false; +} +#endif + +} // namespace + +PlatformChannelPair::PlatformChannelPair(bool client_is_blocking) { + // Create the Unix domain socket. + int fds[2]; + // TODO(vtl): Maybe fail gracefully if |socketpair()| fails. + +#if defined(OS_NACL_SFI) + PCHECK(imc_socketpair(fds) == 0); +#else + PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); + + // Set the ends to nonblocking. + PCHECK(fcntl(fds[0], F_SETFL, O_NONBLOCK) == 0); + if (!client_is_blocking) + PCHECK(fcntl(fds[1], F_SETFL, O_NONBLOCK) == 0); + +#if defined(OS_MACOSX) + // This turns off |SIGPIPE| when writing to a closed socket (causing it to + // fail with |EPIPE| instead). On Linux, we have to use |send...()| with + // |MSG_NOSIGNAL| -- which is not supported on Mac -- instead. + int no_sigpipe = 1; + PCHECK(setsockopt(fds[0], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, + sizeof(no_sigpipe)) == 0); + PCHECK(setsockopt(fds[1], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, + sizeof(no_sigpipe)) == 0); +#endif // defined(OS_MACOSX) +#endif // defined(OS_NACL_SFI) + + server_handle_.reset(PlatformHandle(fds[0])); + DCHECK(server_handle_.is_valid()); + client_handle_.reset(PlatformHandle(fds[1])); + DCHECK(client_handle_.is_valid()); +} + +// static +ScopedPlatformHandle PlatformChannelPair::PassClientHandleFromParentProcess( + const base::CommandLine& command_line) { + std::string client_fd_string = + command_line.GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + return PassClientHandleFromParentProcessFromString(client_fd_string); +} + +ScopedPlatformHandle +PlatformChannelPair::PassClientHandleFromParentProcessFromString( + const std::string& value) { + int client_fd = -1; +#if defined(OS_ANDROID) || defined(__ANDROID__) + base::GlobalDescriptors::Key key = -1; + if (value.empty() || !base::StringToUint(value, &key)) { + LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch; + return ScopedPlatformHandle(); + } + client_fd = base::GlobalDescriptors::GetInstance()->Get(key); +#else + if (value.empty() || + !base::StringToInt(value, &client_fd) || + client_fd < base::GlobalDescriptors::kBaseDescriptor) { + LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch; + return ScopedPlatformHandle(); + } +#endif + return ScopedPlatformHandle(PlatformHandle(client_fd)); +} + +void PlatformChannelPair::PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line, + base::FileHandleMappingVector* handle_passing_info) const { + DCHECK(command_line); + + // Log a warning if the command line already has the switch, but "clobber" it + // anyway, since it's reasonably likely that all the switches were just copied + // from the parent. + LOG_IF(WARNING, command_line->HasSwitch(kMojoPlatformChannelHandleSwitch)) + << "Child command line already has switch --" + << kMojoPlatformChannelHandleSwitch << "=" + << command_line->GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + // (Any existing switch won't actually be removed from the command line, but + // the last one appended takes precedence.) + command_line->AppendSwitchASCII( + kMojoPlatformChannelHandleSwitch, + PrepareToPassClientHandleToChildProcessAsString(handle_passing_info)); +} + +std::string +PlatformChannelPair::PrepareToPassClientHandleToChildProcessAsString( + HandlePassingInformation* handle_passing_info) const { +#if defined(OS_ANDROID) || defined(__ANDROID__) + int fd = client_handle_.get().handle; + handle_passing_info->push_back( + std::pair(fd, kAndroidClientHandleDescriptor)); + return base::UintToString(kAndroidClientHandleDescriptor); +#else + DCHECK(handle_passing_info); + // This is an arbitrary sanity check. (Note that this guarantees that the loop + // below will terminate sanely.) + CHECK_LT(handle_passing_info->size(), 1000u); + + DCHECK(client_handle_.is_valid()); + + // Find a suitable FD to map our client handle to in the child process. + // This has quadratic time complexity in the size of |*handle_passing_info|, + // but |*handle_passing_info| should be very small (usually/often empty). + int target_fd = base::GlobalDescriptors::kBaseDescriptor; + while (IsTargetDescriptorUsed(*handle_passing_info, target_fd)) + target_fd++; + + handle_passing_info->push_back( + std::pair(client_handle_.get().handle, target_fd)); + return base::IntToString(target_fd); +#endif +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc b/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..a3fd275b9ff7f7278843636a1218ab4e9fda1005 --- /dev/null +++ b/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc @@ -0,0 +1,261 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/platform_channel_pair.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/edk/embedder/platform_channel_utils_posix.h" +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/test/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +void WaitReadable(PlatformHandle h) { + struct pollfd pfds = {}; + pfds.fd = h.handle; + pfds.events = POLLIN; + CHECK_EQ(poll(&pfds, 1, -1), 1); +} + +class PlatformChannelPairPosixTest : public testing::Test { + public: + PlatformChannelPairPosixTest() {} + ~PlatformChannelPairPosixTest() override {} + + void SetUp() override { + // Make sure |SIGPIPE| isn't being ignored. + struct sigaction action = {}; + action.sa_handler = SIG_DFL; + ASSERT_EQ(0, sigaction(SIGPIPE, &action, &old_action_)); + } + + void TearDown() override { + // Restore the |SIGPIPE| handler. + ASSERT_EQ(0, sigaction(SIGPIPE, &old_action_, nullptr)); + } + + private: + struct sigaction old_action_; + + DISALLOW_COPY_AND_ASSIGN(PlatformChannelPairPosixTest); +}; + +TEST_F(PlatformChannelPairPosixTest, NoSigPipe) { + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); + + // Write to the client. + static const char kHello[] = "hello"; + EXPECT_EQ(static_cast(sizeof(kHello)), + write(client_handle.get().handle, kHello, sizeof(kHello))); + + // Close the client. + client_handle.reset(); + + // Read from the server; this should be okay. + char buffer[100] = {}; + EXPECT_EQ(static_cast(sizeof(kHello)), + read(server_handle.get().handle, buffer, sizeof(buffer))); + EXPECT_STREQ(kHello, buffer); + + // Try reading again. + ssize_t result = read(server_handle.get().handle, buffer, sizeof(buffer)); + // We should probably get zero (for "end of file"), but -1 would also be okay. + EXPECT_TRUE(result == 0 || result == -1); + if (result == -1) + PLOG(WARNING) << "read (expected 0 for EOF)"; + + // Test our replacement for |write()|/|send()|. + result = PlatformChannelWrite(server_handle.get(), kHello, sizeof(kHello)); + EXPECT_EQ(-1, result); + if (errno != EPIPE) + PLOG(WARNING) << "write (expected EPIPE)"; + + // Test our replacement for |writev()|/|sendv()|. + struct iovec iov[2] = {{const_cast(kHello), sizeof(kHello)}, + {const_cast(kHello), sizeof(kHello)}}; + result = PlatformChannelWritev(server_handle.get(), iov, 2); + EXPECT_EQ(-1, result); + if (errno != EPIPE) + PLOG(WARNING) << "write (expected EPIPE)"; +} + +TEST_F(PlatformChannelPairPosixTest, SendReceiveData) { + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); + + for (size_t i = 0; i < 10; i++) { + std::string send_string(1 << i, 'A' + i); + + EXPECT_EQ(static_cast(send_string.size()), + PlatformChannelWrite(server_handle.get(), send_string.data(), + send_string.size())); + + WaitReadable(client_handle.get()); + + char buf[10000] = {}; + std::deque received_handles; + ssize_t result = PlatformChannelRecvmsg(client_handle.get(), buf, + sizeof(buf), &received_handles); + EXPECT_EQ(static_cast(send_string.size()), result); + EXPECT_EQ(send_string, std::string(buf, static_cast(result))); + EXPECT_TRUE(received_handles.empty()); + } +} + +TEST_F(PlatformChannelPairPosixTest, SendReceiveFDs) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + static const char kHello[] = "hello"; + + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); + +// Reduce the number of FDs opened on OS X to avoid test flake. +#if defined(OS_MACOSX) + const size_t kNumHandlesToSend = kPlatformChannelMaxNumHandles / 2; +#else + const size_t kNumHandlesToSend = kPlatformChannelMaxNumHandles; +#endif + + for (size_t i = 1; i < kNumHandlesToSend; i++) { + // Make |i| files, with the j-th file consisting of j copies of the digit + // |c|. + const char c = '0' + (i % 10); + ScopedPlatformHandleVectorPtr platform_handles(new PlatformHandleVector); + for (size_t j = 1; j <= i; j++) { + base::FilePath unused; + base::ScopedFILE fp( + base::CreateAndOpenTemporaryFileInDir(temp_dir.GetPath(), &unused)); + ASSERT_TRUE(fp); + ASSERT_EQ(j, fwrite(std::string(j, c).data(), 1, j, fp.get())); + platform_handles->push_back( + test::PlatformHandleFromFILE(std::move(fp)).release()); + ASSERT_TRUE(platform_handles->back().is_valid()); + } + + // Send the FDs (+ "hello"). + struct iovec iov = {const_cast(kHello), sizeof(kHello)}; + // We assume that the |sendmsg()| actually sends all the data. + EXPECT_EQ(static_cast(sizeof(kHello)), + PlatformChannelSendmsgWithHandles(server_handle.get(), &iov, 1, + &platform_handles->at(0), + platform_handles->size())); + + WaitReadable(client_handle.get()); + + char buf[10000] = {}; + std::deque received_handles; + // We assume that the |recvmsg()| actually reads all the data. + EXPECT_EQ(static_cast(sizeof(kHello)), + PlatformChannelRecvmsg(client_handle.get(), buf, sizeof(buf), + &received_handles)); + EXPECT_STREQ(kHello, buf); + EXPECT_EQ(i, received_handles.size()); + + for (size_t j = 0; !received_handles.empty(); j++) { + base::ScopedFILE fp(test::FILEFromPlatformHandle( + ScopedPlatformHandle(received_handles.front()), "rb")); + received_handles.pop_front(); + ASSERT_TRUE(fp); + rewind(fp.get()); + char read_buf[kNumHandlesToSend]; + size_t bytes_read = fread(read_buf, 1, sizeof(read_buf), fp.get()); + EXPECT_EQ(j + 1, bytes_read); + EXPECT_EQ(std::string(j + 1, c), std::string(read_buf, bytes_read)); + } + } +} + +TEST_F(PlatformChannelPairPosixTest, AppendReceivedFDs) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + static const char kHello[] = "hello"; + + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); + + const std::string file_contents("hello world"); + + { + base::FilePath unused; + base::ScopedFILE fp( + base::CreateAndOpenTemporaryFileInDir(temp_dir.GetPath(), &unused)); + ASSERT_TRUE(fp); + ASSERT_EQ(file_contents.size(), + fwrite(file_contents.data(), 1, file_contents.size(), fp.get())); + ScopedPlatformHandleVectorPtr platform_handles(new PlatformHandleVector); + platform_handles->push_back( + test::PlatformHandleFromFILE(std::move(fp)).release()); + ASSERT_TRUE(platform_handles->back().is_valid()); + + // Send the FD (+ "hello"). + struct iovec iov = {const_cast(kHello), sizeof(kHello)}; + // We assume that the |sendmsg()| actually sends all the data. + EXPECT_EQ(static_cast(sizeof(kHello)), + PlatformChannelSendmsgWithHandles(server_handle.get(), &iov, 1, + &platform_handles->at(0), + platform_handles->size())); + } + + WaitReadable(client_handle.get()); + + // Start with an invalid handle in the deque. + std::deque received_handles; + received_handles.push_back(PlatformHandle()); + + char buf[100] = {}; + // We assume that the |recvmsg()| actually reads all the data. + EXPECT_EQ(static_cast(sizeof(kHello)), + PlatformChannelRecvmsg(client_handle.get(), buf, sizeof(buf), + &received_handles)); + EXPECT_STREQ(kHello, buf); + ASSERT_EQ(2u, received_handles.size()); + EXPECT_FALSE(received_handles[0].is_valid()); + EXPECT_TRUE(received_handles[1].is_valid()); + + { + base::ScopedFILE fp(test::FILEFromPlatformHandle( + ScopedPlatformHandle(received_handles[1]), "rb")); + received_handles[1] = PlatformHandle(); + ASSERT_TRUE(fp); + rewind(fp.get()); + char read_buf[100]; + size_t bytes_read = fread(read_buf, 1, sizeof(read_buf), fp.get()); + EXPECT_EQ(file_contents.size(), bytes_read); + EXPECT_EQ(file_contents, std::string(read_buf, bytes_read)); + } +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_channel_pair_win.cc b/mojo/edk/embedder/platform_channel_pair_win.cc new file mode 100644 index 0000000000000000000000000000000000000000..f523ade33576516442a6e08d933bb818fd65adb8 --- /dev/null +++ b/mojo/edk/embedder/platform_channel_pair_win.cc @@ -0,0 +1,123 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/platform_channel_pair.h" + +#include + +#include + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/win/windows_version.h" +#include "mojo/edk/embedder/platform_handle.h" + +namespace mojo { +namespace edk { + +namespace { + +std::wstring GeneratePipeName() { + return base::StringPrintf(L"\\\\.\\pipe\\mojo.%u.%u.%I64u", + GetCurrentProcessId(), GetCurrentThreadId(), + base::RandUint64()); +} + +} // namespace + +PlatformChannelPair::PlatformChannelPair(bool client_is_blocking) { + std::wstring pipe_name = GeneratePipeName(); + + DWORD kOpenMode = + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE; + const DWORD kPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE; + server_handle_.reset(PlatformHandle( + CreateNamedPipeW(pipe_name.c_str(), kOpenMode, kPipeMode, + 1, // Max instances. + 4096, // Out buffer size. + 4096, // In buffer size. + 5000, // Timeout in milliseconds. + nullptr))); // Default security descriptor. + PCHECK(server_handle_.is_valid()); + + const DWORD kDesiredAccess = GENERIC_READ | GENERIC_WRITE; + // The SECURITY_ANONYMOUS flag means that the server side cannot impersonate + // the client. + DWORD kFlags = SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS; + if (!client_is_blocking) + kFlags |= FILE_FLAG_OVERLAPPED; + // Allow the handle to be inherited by child processes. + SECURITY_ATTRIBUTES security_attributes = { + sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE}; + client_handle_.reset( + PlatformHandle(CreateFileW(pipe_name.c_str(), kDesiredAccess, + 0, // No sharing. + &security_attributes, OPEN_EXISTING, kFlags, + nullptr))); // No template file. + PCHECK(client_handle_.is_valid()); + + // Since a client has connected, ConnectNamedPipe() should return zero and + // GetLastError() should return ERROR_PIPE_CONNECTED. + CHECK(!ConnectNamedPipe(server_handle_.get().handle, nullptr)); + PCHECK(GetLastError() == ERROR_PIPE_CONNECTED); +} + +// static +ScopedPlatformHandle PlatformChannelPair::PassClientHandleFromParentProcess( + const base::CommandLine& command_line) { + std::string client_handle_string = + command_line.GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + return PassClientHandleFromParentProcessFromString(client_handle_string); +} + +ScopedPlatformHandle +PlatformChannelPair::PassClientHandleFromParentProcessFromString( + const std::string& value) { + int client_handle_value = 0; + if (value.empty() || + !base::StringToInt(value, &client_handle_value)) { + LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch; + return ScopedPlatformHandle(); + } + + return ScopedPlatformHandle( + PlatformHandle(LongToHandle(client_handle_value))); +} + +void PlatformChannelPair::PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line, + base::HandlesToInheritVector* handle_passing_info) const { + DCHECK(command_line); + + // Log a warning if the command line already has the switch, but "clobber" it + // anyway, since it's reasonably likely that all the switches were just copied + // from the parent. + LOG_IF(WARNING, command_line->HasSwitch(kMojoPlatformChannelHandleSwitch)) + << "Child command line already has switch --" + << kMojoPlatformChannelHandleSwitch << "=" + << command_line->GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + // (Any existing switch won't actually be removed from the command line, but + // the last one appended takes precedence.) + command_line->AppendSwitchASCII( + kMojoPlatformChannelHandleSwitch, + PrepareToPassClientHandleToChildProcessAsString(handle_passing_info)); +} + +std::string +PlatformChannelPair::PrepareToPassClientHandleToChildProcessAsString( + HandlePassingInformation* handle_passing_info) const { + DCHECK(handle_passing_info); + DCHECK(client_handle_.is_valid()); + + if (base::win::GetVersion() >= base::win::VERSION_VISTA) + handle_passing_info->push_back(client_handle_.get().handle); + + return base::IntToString(HandleToLong(client_handle_.get().handle)); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_channel_utils_posix.cc b/mojo/edk/embedder/platform_channel_utils_posix.cc new file mode 100644 index 0000000000000000000000000000000000000000..689b6eec0dc9585a32b6edd2c3fef2a35d2d080b --- /dev/null +++ b/mojo/edk/embedder/platform_channel_utils_posix.cc @@ -0,0 +1,282 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/platform_channel_utils_posix.h" + +#include +#include +#include + +#include + +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" + +#if !defined(OS_NACL) +#include +#endif + +#if !defined(SO_PEEK_OFF) +#define SO_PEEK_OFF 42 +#endif + +namespace mojo { +namespace edk { +namespace { + +#if !defined(OS_NACL) +bool IsRecoverableError() { + return errno == ECONNABORTED || errno == EMFILE || errno == ENFILE || + errno == ENOMEM || errno == ENOBUFS; +} + +bool GetPeerEuid(PlatformHandle handle, uid_t* peer_euid) { + DCHECK(peer_euid); +#if defined(OS_MACOSX) || defined(OS_OPENBSD) || defined(OS_FREEBSD) + uid_t socket_euid; + gid_t socket_gid; + if (getpeereid(handle.handle, &socket_euid, &socket_gid) < 0) { + PLOG(ERROR) << "getpeereid " << handle.handle; + return false; + } + *peer_euid = socket_euid; + return true; +#else + struct ucred cred; + socklen_t cred_len = sizeof(cred); + if (getsockopt(handle.handle, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) < + 0) { + PLOG(ERROR) << "getsockopt " << handle.handle; + return false; + } + if (static_cast(cred_len) < sizeof(cred)) { + NOTREACHED() << "Truncated ucred from SO_PEERCRED?"; + return false; + } + *peer_euid = cred.uid; + return true; +#endif +} + +bool IsPeerAuthorized(PlatformHandle peer_handle) { + uid_t peer_euid; + if (!GetPeerEuid(peer_handle, &peer_euid)) + return false; + if (peer_euid != geteuid()) { + DLOG(ERROR) << "Client euid is not authorised"; + return false; + } + return true; +} +#endif // !defined(OS_NACL) + +} // namespace + +// On Linux, |SIGPIPE| is suppressed by passing |MSG_NOSIGNAL| to +// |send()|/|sendmsg()|. (There is no way of suppressing |SIGPIPE| on +// |write()|/|writev().) On Mac, |SIGPIPE| is suppressed by setting the +// |SO_NOSIGPIPE| option on the socket. +// +// Performance notes: +// - On Linux, we have to use |send()|/|sendmsg()| rather than +// |write()|/|writev()| in order to suppress |SIGPIPE|. This is okay, since +// |send()| is (slightly) faster than |write()| (!), while |sendmsg()| is +// quite comparable to |writev()|. +// - On Mac, we may use |write()|/|writev()|. Here, |write()| is considerably +// faster than |send()|, whereas |sendmsg()| is quite comparable to +// |writev()|. +// - On both platforms, an appropriate |sendmsg()|/|writev()| is considerably +// faster than two |send()|s/|write()|s. +// - Relative numbers (minimum real times from 10 runs) for one |write()| of +// 1032 bytes, one |send()| of 1032 bytes, one |writev()| of 32+1000 bytes, +// one |sendmsg()| of 32+1000 bytes, two |write()|s of 32 and 1000 bytes, two +// |send()|s of 32 and 1000 bytes: +// - Linux: 0.81 s, 0.77 s, 0.87 s, 0.89 s, 1.31 s, 1.22 s +// - Mac: 2.21 s, 2.91 s, 2.98 s, 3.08 s, 3.59 s, 4.74 s + +// Flags to use with calling |send()| or |sendmsg()| (see above). +#if defined(OS_MACOSX) +const int kSendFlags = 0; +#else +const int kSendFlags = MSG_NOSIGNAL; +#endif + +ssize_t PlatformChannelWrite(PlatformHandle h, + const void* bytes, + size_t num_bytes) { + DCHECK(h.is_valid()); + DCHECK(bytes); + DCHECK_GT(num_bytes, 0u); + +#if defined(OS_MACOSX) || defined(OS_NACL_NONSFI) + // send() is not available under NaCl-nonsfi. + return HANDLE_EINTR(write(h.handle, bytes, num_bytes)); +#else + return send(h.handle, bytes, num_bytes, kSendFlags); +#endif +} + +ssize_t PlatformChannelWritev(PlatformHandle h, + struct iovec* iov, + size_t num_iov) { + DCHECK(h.is_valid()); + DCHECK(iov); + DCHECK_GT(num_iov, 0u); + +#if defined(OS_MACOSX) + return HANDLE_EINTR(writev(h.handle, iov, static_cast(num_iov))); +#else + struct msghdr msg = {}; + msg.msg_iov = iov; + msg.msg_iovlen = num_iov; + return HANDLE_EINTR(sendmsg(h.handle, &msg, kSendFlags)); +#endif +} + +ssize_t PlatformChannelSendmsgWithHandles(PlatformHandle h, + struct iovec* iov, + size_t num_iov, + PlatformHandle* platform_handles, + size_t num_platform_handles) { + DCHECK(iov); + DCHECK_GT(num_iov, 0u); + DCHECK(platform_handles); + DCHECK_GT(num_platform_handles, 0u); + DCHECK_LE(num_platform_handles, kPlatformChannelMaxNumHandles); + + char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))]; + struct msghdr msg = {}; + msg.msg_iov = iov; + msg.msg_iovlen = num_iov; + msg.msg_control = cmsg_buf; + msg.msg_controllen = CMSG_LEN(num_platform_handles * sizeof(int)); + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(num_platform_handles * sizeof(int)); + for (size_t i = 0; i < num_platform_handles; i++) { + DCHECK(platform_handles[i].is_valid()); + reinterpret_cast(CMSG_DATA(cmsg))[i] = platform_handles[i].handle; + } + + return HANDLE_EINTR(sendmsg(h.handle, &msg, kSendFlags)); +} + +bool PlatformChannelSendHandles(PlatformHandle h, + PlatformHandle* handles, + size_t num_handles) { + DCHECK(handles); + DCHECK_GT(num_handles, 0u); + DCHECK_LE(num_handles, kPlatformChannelMaxNumHandles); + + // Note: |sendmsg()| fails on Mac if we don't write at least one character. + struct iovec iov = {const_cast(""), 1}; + char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))]; + struct msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = CMSG_LEN(num_handles * sizeof(int)); + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(num_handles * sizeof(int)); + for (size_t i = 0; i < num_handles; i++) { + DCHECK(handles[i].is_valid()); + reinterpret_cast(CMSG_DATA(cmsg))[i] = handles[i].handle; + } + + ssize_t result = HANDLE_EINTR(sendmsg(h.handle, &msg, kSendFlags)); + if (result < 1) { + DCHECK_EQ(result, -1); + return false; + } + + for (size_t i = 0; i < num_handles; i++) + handles[i].CloseIfNecessary(); + return true; +} + +ssize_t PlatformChannelRecvmsg(PlatformHandle h, + void* buf, + size_t num_bytes, + std::deque* platform_handles, + bool block) { + DCHECK(buf); + DCHECK_GT(num_bytes, 0u); + DCHECK(platform_handles); + + struct iovec iov = {buf, num_bytes}; + char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))]; + struct msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + + ssize_t result = + HANDLE_EINTR(recvmsg(h.handle, &msg, block ? 0 : MSG_DONTWAIT)); + if (result < 0) + return result; + + // Success; no control messages. + if (msg.msg_controllen == 0) + return result; + + DCHECK(!(msg.msg_flags & MSG_CTRUNC)); + + for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + size_t payload_length = cmsg->cmsg_len - CMSG_LEN(0); + DCHECK_EQ(payload_length % sizeof(int), 0u); + size_t num_fds = payload_length / sizeof(int); + const int* fds = reinterpret_cast(CMSG_DATA(cmsg)); + for (size_t i = 0; i < num_fds; i++) { + platform_handles->push_back(PlatformHandle(fds[i])); + DCHECK(platform_handles->back().is_valid()); + } + } + } + + return result; +} + +bool ServerAcceptConnection(PlatformHandle server_handle, + ScopedPlatformHandle* connection_handle, + bool check_peer_user) { + DCHECK(server_handle.is_valid()); + connection_handle->reset(); +#if defined(OS_NACL) + NOTREACHED(); + return false; +#else + ScopedPlatformHandle accept_handle( + PlatformHandle(HANDLE_EINTR(accept(server_handle.handle, NULL, 0)))); + if (!accept_handle.is_valid()) + return IsRecoverableError(); + + // Verify that the IPC channel peer is running as the same user. + if (check_peer_user && !IsPeerAuthorized(accept_handle.get())) { + return true; + } + + if (!base::SetNonBlocking(accept_handle.get().handle)) { + PLOG(ERROR) << "base::SetNonBlocking() failed " + << accept_handle.get().handle; + // It's safe to keep listening on |server_handle| even if the attempt to set + // O_NONBLOCK failed on the client fd. + return true; + } + + *connection_handle = std::move(accept_handle); + return true; +#endif // defined(OS_NACL) +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_channel_utils_posix.h b/mojo/edk/embedder/platform_channel_utils_posix.h new file mode 100644 index 0000000000000000000000000000000000000000..23cfa92a344945f29558305ae00962c5068f14a3 --- /dev/null +++ b/mojo/edk/embedder/platform_channel_utils_posix.h @@ -0,0 +1,87 @@ +// Copyright 2014 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 MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_ + +#include +#include // For |ssize_t|. + +#include +#include + +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +struct iovec; // Declared in . + +namespace mojo { +namespace edk { +class ScopedPlatformHandle; + +// The maximum number of handles that can be sent "at once" using +// |PlatformChannelSendmsgWithHandles()|. This must be less than the Linux +// kernel's SCM_MAX_FD which is 253. +const size_t kPlatformChannelMaxNumHandles = 128; + +// Use these to write to a socket created using |PlatformChannelPair| (or +// equivalent). These are like |write()| and |writev()|, but handle |EINTR| and +// never raise |SIGPIPE|. (Note: On Mac, the suppression of |SIGPIPE| is set up +// by |PlatformChannelPair|.) +MOJO_SYSTEM_IMPL_EXPORT ssize_t +PlatformChannelWrite(PlatformHandle h, const void* bytes, size_t num_bytes); +MOJO_SYSTEM_IMPL_EXPORT ssize_t +PlatformChannelWritev(PlatformHandle h, struct iovec* iov, size_t num_iov); + +// Writes data, and the given set of |PlatformHandle|s (i.e., file descriptors) +// over the Unix domain socket given by |h| (e.g., created using +// |PlatformChannelPair()|). All the handles must be valid, and there must be at +// least one and at most |kPlatformChannelMaxNumHandles| handles. The return +// value is as for |sendmsg()|, namely -1 on failure and otherwise the number of +// bytes of data sent on success (note that this may not be all the data +// specified by |iov|). (The handles are not closed, regardless of success or +// failure.) +MOJO_SYSTEM_IMPL_EXPORT ssize_t +PlatformChannelSendmsgWithHandles(PlatformHandle h, + struct iovec* iov, + size_t num_iov, + PlatformHandle* platform_handles, + size_t num_platform_handles); + +// TODO(vtl): Remove this once I've switched things over to +// |PlatformChannelSendmsgWithHandles()|. +// Sends |PlatformHandle|s (i.e., file descriptors) over the Unix domain socket +// (e.g., created using PlatformChannelPair|). (These will be sent in a single +// message having one null byte of data and one control message header with all +// the file descriptors.) All of the handles must be valid, and there must be at +// most |kPlatformChannelMaxNumHandles| (and at least one handle). Returns true +// on success, in which case it closes all the handles. +MOJO_SYSTEM_IMPL_EXPORT bool PlatformChannelSendHandles(PlatformHandle h, + PlatformHandle* handles, + size_t num_handles); + +// Wrapper around |recvmsg()|, which will extract any attached file descriptors +// (in the control message) to |PlatformHandle|s (and append them to +// |platform_handles|). (This also handles |EINTR|.) +MOJO_SYSTEM_IMPL_EXPORT ssize_t +PlatformChannelRecvmsg(PlatformHandle h, + void* buf, + size_t num_bytes, + std::deque* platform_handles, + bool block = false); + +// Returns false if |server_handle| encounters an unrecoverable error. +// Returns true if it's valid to keep listening on |server_handle|. In this +// case, it's possible that a connection wasn't successfully established; then, +// |connection_handle| will be invalid. If |check_peer_user| is True, the +// connection will be rejected if the peer is running as a different user. +MOJO_SYSTEM_IMPL_EXPORT bool ServerAcceptConnection( + PlatformHandle server_handle, + ScopedPlatformHandle* connection_handle, + bool check_peer_user = true); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_ diff --git a/mojo/edk/embedder/platform_handle.cc b/mojo/edk/embedder/platform_handle.cc new file mode 100644 index 0000000000000000000000000000000000000000..b6b2cd22d1fee38d8f38879adda8d387a7f6a237 --- /dev/null +++ b/mojo/edk/embedder/platform_handle.cc @@ -0,0 +1,74 @@ +// Copyright 2013 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. + +#include "mojo/edk/embedder/platform_handle.h" + +#include "build/build_config.h" +#if defined(OS_POSIX) +#include +#elif defined(OS_WIN) +#include +#else +#error "Platform not yet supported." +#endif + +#include "base/logging.h" + +namespace mojo { +namespace edk { + +void PlatformHandle::CloseIfNecessary() { + if (!is_valid()) + return; + +#if defined(OS_POSIX) + if (type == Type::POSIX) { + bool success = (close(handle) == 0); + DPCHECK(success); + handle = -1; + } +#if defined(OS_MACOSX) && !defined(OS_IOS) + else if (type == Type::MACH) { + kern_return_t rv = mach_port_deallocate(mach_task_self(), port); + DPCHECK(rv == KERN_SUCCESS); + port = MACH_PORT_NULL; + } +#endif // defined(OS_MACOSX) && !defined(OS_IOS) +#elif defined(OS_WIN) + if (owning_process != base::GetCurrentProcessHandle()) { + // This handle may have been duplicated to a new target process but not yet + // sent there. In this case CloseHandle should NOT be called. From MSDN + // documentation for DuplicateHandle[1]: + // + // Normally the target process closes a duplicated handle when that + // process is finished using the handle. To close a duplicated handle + // from the source process, call DuplicateHandle with the following + // parameters: + // + // * Set hSourceProcessHandle to the target process from the + // call that created the handle. + // * Set hSourceHandle to the duplicated handle to close. + // * Set lpTargetHandle to NULL. + // * Set dwOptions to DUPLICATE_CLOSE_SOURCE. + // + // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/ms724251 + // + // NOTE: It's possible for this operation to fail if the owning process + // was terminated or is in the process of being terminated. Either way, + // there is nothing we can reasonably do about failure, so we ignore it. + DuplicateHandle(owning_process, handle, NULL, &handle, 0, FALSE, + DUPLICATE_CLOSE_SOURCE); + return; + } + + bool success = !!CloseHandle(handle); + DPCHECK(success); + handle = INVALID_HANDLE_VALUE; +#else +#error "Platform not yet supported." +#endif +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_handle.h b/mojo/edk/embedder/platform_handle.h new file mode 100644 index 0000000000000000000000000000000000000000..4866e754fd1376579a06333d4d49d331cd9c33bc --- /dev/null +++ b/mojo/edk/embedder/platform_handle.h @@ -0,0 +1,90 @@ +// Copyright 2013 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 MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_H_ + +#include "build/build_config.h" +#include "mojo/edk/system/system_impl_export.h" + +#if defined(OS_WIN) +#include + +#include "base/process/process_handle.h" +#elif defined(OS_MACOSX) && !defined(OS_IOS) +#include +#endif + +namespace mojo { +namespace edk { + +#if defined(OS_POSIX) +struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle { + PlatformHandle() {} + explicit PlatformHandle(int handle) : handle(handle) {} +#if defined(OS_MACOSX) && !defined(OS_IOS) + explicit PlatformHandle(mach_port_t port) + : type(Type::MACH), port(port) {} +#endif + + void CloseIfNecessary(); + + bool is_valid() const { +#if defined(OS_MACOSX) && !defined(OS_IOS) + if (type == Type::MACH || type == Type::MACH_NAME) + return port != MACH_PORT_NULL; +#endif + return handle != -1; + } + + enum class Type { + POSIX, +#if defined(OS_MACOSX) && !defined(OS_IOS) + MACH, + // MACH_NAME isn't a real Mach port. But rather the "name" of one that can + // be resolved to a real port later. This distinction is needed so that the + // "port" doesn't try to be closed if CloseIfNecessary() is called. Having + // this also allows us to do checks in other places. + MACH_NAME, +#endif + }; + Type type = Type::POSIX; + + int handle = -1; + + // A POSIX handle may be a listen handle that can accept a connection. + bool needs_connection = false; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + mach_port_t port = MACH_PORT_NULL; +#endif +}; +#elif defined(OS_WIN) +struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle { + PlatformHandle() : PlatformHandle(INVALID_HANDLE_VALUE) {} + explicit PlatformHandle(HANDLE handle) + : handle(handle), owning_process(base::GetCurrentProcessHandle()) {} + + void CloseIfNecessary(); + + bool is_valid() const { return handle != INVALID_HANDLE_VALUE; } + + HANDLE handle; + + // A Windows HANDLE may be duplicated to another process but not yet sent to + // that process. This tracks the handle's owning process. + base::ProcessHandle owning_process; + + // A Windows HANDLE may be an unconnected named pipe. In this case, we need to + // wait for a connection before communicating on the pipe. + bool needs_connection = false; +}; +#else +#error "Platform not yet supported." +#endif + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_H_ diff --git a/mojo/edk/embedder/platform_handle_utils.h b/mojo/edk/embedder/platform_handle_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..fa683e4a606e5daf1609aa0f76416cccb11c0361 --- /dev/null +++ b/mojo/edk/embedder/platform_handle_utils.h @@ -0,0 +1,33 @@ +// Copyright 2014 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 MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_ + +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +// Closes all the |PlatformHandle|s in the given container. +template +MOJO_SYSTEM_IMPL_EXPORT inline void CloseAllPlatformHandles( + PlatformHandleContainer* platform_handles) { + for (typename PlatformHandleContainer::iterator it = + platform_handles->begin(); + it != platform_handles->end(); ++it) + it->CloseIfNecessary(); +} + +// Duplicates the given |PlatformHandle| (which must be valid). (Returns an +// invalid |ScopedPlatformHandle| on failure.) +MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle +DuplicatePlatformHandle(PlatformHandle platform_handle); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_ diff --git a/mojo/edk/embedder/platform_handle_utils_posix.cc b/mojo/edk/embedder/platform_handle_utils_posix.cc new file mode 100644 index 0000000000000000000000000000000000000000..5604f96bf14631fea0ae820a14d608782ce6c99b --- /dev/null +++ b/mojo/edk/embedder/platform_handle_utils_posix.cc @@ -0,0 +1,24 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/platform_handle_utils.h" + +#include + +#include "base/logging.h" + +namespace mojo { +namespace edk { + +ScopedPlatformHandle DuplicatePlatformHandle(PlatformHandle platform_handle) { + DCHECK(platform_handle.is_valid()); + // Note that |dup()| returns -1 on error (which is exactly the value we use + // for invalid |PlatformHandle| FDs). + PlatformHandle duped(dup(platform_handle.handle)); + duped.needs_connection = platform_handle.needs_connection; + return ScopedPlatformHandle(duped); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_handle_utils_win.cc b/mojo/edk/embedder/platform_handle_utils_win.cc new file mode 100644 index 0000000000000000000000000000000000000000..32ed49afc18a519ebc1b78bc1acfb50051d8493d --- /dev/null +++ b/mojo/edk/embedder/platform_handle_utils_win.cc @@ -0,0 +1,28 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/platform_handle_utils.h" + +#include + +#include "base/logging.h" + +namespace mojo { +namespace edk { + +ScopedPlatformHandle DuplicatePlatformHandle(PlatformHandle platform_handle) { + DCHECK(platform_handle.is_valid()); + + HANDLE new_handle; + CHECK_NE(platform_handle.handle, INVALID_HANDLE_VALUE); + if (!DuplicateHandle(GetCurrentProcess(), platform_handle.handle, + GetCurrentProcess(), &new_handle, 0, TRUE, + DUPLICATE_SAME_ACCESS)) + return ScopedPlatformHandle(); + DCHECK_NE(new_handle, INVALID_HANDLE_VALUE); + return ScopedPlatformHandle(PlatformHandle(new_handle)); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_handle_vector.h b/mojo/edk/embedder/platform_handle_vector.h new file mode 100644 index 0000000000000000000000000000000000000000..9892b23cac0a0fa960d4fc0fb563fd94aa7676aa --- /dev/null +++ b/mojo/edk/embedder/platform_handle_vector.h @@ -0,0 +1,35 @@ +// Copyright 2014 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 MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_ + +#include +#include + +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/platform_handle_utils.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +using PlatformHandleVector = std::vector; + +// A deleter (for use with |scoped_ptr|) which closes all handles and then +// |delete|s the |PlatformHandleVector|. +struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandleVectorDeleter { + void operator()(PlatformHandleVector* platform_handles) const { + CloseAllPlatformHandles(platform_handles); + delete platform_handles; + } +}; + +using ScopedPlatformHandleVectorPtr = + std::unique_ptr; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_ diff --git a/mojo/edk/embedder/platform_shared_buffer.cc b/mojo/edk/embedder/platform_shared_buffer.cc new file mode 100644 index 0000000000000000000000000000000000000000..58af44df8f8c2763ce50a135c01b744e62fdd844 --- /dev/null +++ b/mojo/edk/embedder/platform_shared_buffer.cc @@ -0,0 +1,325 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/platform_shared_buffer.h" + +#include + +#include + +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/memory/shared_memory.h" +#include "base/process/process_handle.h" +#include "base/sys_info.h" +#include "mojo/edk/embedder/platform_handle_utils.h" + +#if defined(OS_NACL) +// For getpagesize() on NaCl. +#include +#endif + +namespace mojo { +namespace edk { + +namespace { + +// Takes ownership of |memory_handle|. +ScopedPlatformHandle SharedMemoryToPlatformHandle( + base::SharedMemoryHandle memory_handle) { +#if defined(OS_POSIX) && !(defined(OS_MACOSX) && !defined(OS_IOS)) + return ScopedPlatformHandle(PlatformHandle(memory_handle.fd)); +#elif defined(OS_WIN) + return ScopedPlatformHandle(PlatformHandle(memory_handle.GetHandle())); +#else + return ScopedPlatformHandle(PlatformHandle(memory_handle.GetMemoryObject())); +#endif +} + +} // namespace + +// static +PlatformSharedBuffer* PlatformSharedBuffer::Create(size_t num_bytes) { + DCHECK_GT(num_bytes, 0u); + + PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, false); + if (!rv->Init()) { + // We can't just delete it directly, due to the "in destructor" (debug) + // check. + scoped_refptr deleter(rv); + return nullptr; + } + + return rv; +} + +// static +PlatformSharedBuffer* PlatformSharedBuffer::CreateFromPlatformHandle( + size_t num_bytes, + bool read_only, + ScopedPlatformHandle platform_handle) { + DCHECK_GT(num_bytes, 0u); + + PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, read_only); + if (!rv->InitFromPlatformHandle(std::move(platform_handle))) { + // We can't just delete it directly, due to the "in destructor" (debug) + // check. + scoped_refptr deleter(rv); + return nullptr; + } + + return rv; +} + +// static +PlatformSharedBuffer* PlatformSharedBuffer::CreateFromPlatformHandlePair( + size_t num_bytes, + ScopedPlatformHandle rw_platform_handle, + ScopedPlatformHandle ro_platform_handle) { + DCHECK_GT(num_bytes, 0u); + DCHECK(rw_platform_handle.is_valid()); + DCHECK(ro_platform_handle.is_valid()); + + PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, false); + if (!rv->InitFromPlatformHandlePair(std::move(rw_platform_handle), + std::move(ro_platform_handle))) { + // We can't just delete it directly, due to the "in destructor" (debug) + // check. + scoped_refptr deleter(rv); + return nullptr; + } + + return rv; +} + +// static +PlatformSharedBuffer* PlatformSharedBuffer::CreateFromSharedMemoryHandle( + size_t num_bytes, + bool read_only, + base::SharedMemoryHandle handle) { + DCHECK_GT(num_bytes, 0u); + + PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, read_only); + rv->InitFromSharedMemoryHandle(handle); + + return rv; +} + +size_t PlatformSharedBuffer::GetNumBytes() const { + return num_bytes_; +} + +bool PlatformSharedBuffer::IsReadOnly() const { + return read_only_; +} + +std::unique_ptr PlatformSharedBuffer::Map( + size_t offset, + size_t length) { + if (!IsValidMap(offset, length)) + return nullptr; + + return MapNoCheck(offset, length); +} + +bool PlatformSharedBuffer::IsValidMap(size_t offset, size_t length) { + if (offset > num_bytes_ || length == 0) + return false; + + // Note: This is an overflow-safe check of |offset + length > num_bytes_| + // (that |num_bytes >= offset| is verified above). + if (length > num_bytes_ - offset) + return false; + + return true; +} + +std::unique_ptr PlatformSharedBuffer::MapNoCheck( + size_t offset, + size_t length) { + DCHECK(IsValidMap(offset, length)); + DCHECK(shared_memory_); + base::SharedMemoryHandle handle; + { + base::AutoLock locker(lock_); + handle = base::SharedMemory::DuplicateHandle(shared_memory_->handle()); + } + if (handle == base::SharedMemory::NULLHandle()) + return nullptr; + + std::unique_ptr mapping( + new PlatformSharedBufferMapping(handle, read_only_, offset, length)); + if (mapping->Map()) + return base::WrapUnique(mapping.release()); + + return nullptr; +} + +ScopedPlatformHandle PlatformSharedBuffer::DuplicatePlatformHandle() { + DCHECK(shared_memory_); + base::SharedMemoryHandle handle; + { + base::AutoLock locker(lock_); + handle = base::SharedMemory::DuplicateHandle(shared_memory_->handle()); + } + if (handle == base::SharedMemory::NULLHandle()) + return ScopedPlatformHandle(); + + return SharedMemoryToPlatformHandle(handle); +} + +ScopedPlatformHandle PlatformSharedBuffer::PassPlatformHandle() { + DCHECK(HasOneRef()); + + // The only way to pass a handle from base::SharedMemory is to duplicate it + // and close the original. + ScopedPlatformHandle handle = DuplicatePlatformHandle(); + + base::AutoLock locker(lock_); + shared_memory_->Close(); + return handle; +} + +base::SharedMemoryHandle PlatformSharedBuffer::DuplicateSharedMemoryHandle() { + DCHECK(shared_memory_); + + base::AutoLock locker(lock_); + return base::SharedMemory::DuplicateHandle(shared_memory_->handle()); +} + +PlatformSharedBuffer* PlatformSharedBuffer::CreateReadOnlyDuplicate() { + DCHECK(shared_memory_); + + if (ro_shared_memory_) { + base::AutoLock locker(lock_); + base::SharedMemoryHandle handle; + handle = base::SharedMemory::DuplicateHandle(ro_shared_memory_->handle()); + if (handle == base::SharedMemory::NULLHandle()) + return nullptr; + return CreateFromSharedMemoryHandle(num_bytes_, true, handle); + } + + base::SharedMemoryHandle handle; + bool success; + { + base::AutoLock locker(lock_); + success = shared_memory_->ShareReadOnlyToProcess( + base::GetCurrentProcessHandle(), &handle); + } + if (!success || handle == base::SharedMemory::NULLHandle()) + return nullptr; + + return CreateFromSharedMemoryHandle(num_bytes_, true, handle); +} + +PlatformSharedBuffer::PlatformSharedBuffer(size_t num_bytes, bool read_only) + : num_bytes_(num_bytes), read_only_(read_only) {} + +PlatformSharedBuffer::~PlatformSharedBuffer() {} + +bool PlatformSharedBuffer::Init() { + DCHECK(!shared_memory_); + DCHECK(!read_only_); + + base::SharedMemoryCreateOptions options; + options.size = num_bytes_; + // By default, we can share as read-only. + options.share_read_only = true; + + shared_memory_.reset(new base::SharedMemory); + return shared_memory_->Create(options); +} + +bool PlatformSharedBuffer::InitFromPlatformHandle( + ScopedPlatformHandle platform_handle) { + DCHECK(!shared_memory_); + +#if defined(OS_WIN) + base::SharedMemoryHandle handle(platform_handle.release().handle, + base::GetCurrentProcId()); +#elif defined(OS_MACOSX) && !defined(OS_IOS) + base::SharedMemoryHandle handle; + handle = base::SharedMemoryHandle(platform_handle.release().port, num_bytes_, + base::GetCurrentProcId()); +#else + base::SharedMemoryHandle handle(platform_handle.release().handle, false); +#endif + + shared_memory_.reset(new base::SharedMemory(handle, read_only_)); + return true; +} + +bool PlatformSharedBuffer::InitFromPlatformHandlePair( + ScopedPlatformHandle rw_platform_handle, + ScopedPlatformHandle ro_platform_handle) { +#if defined(OS_MACOSX) + NOTREACHED(); + return false; +#else // defined(OS_MACOSX) + +#if defined(OS_WIN) + base::SharedMemoryHandle handle(rw_platform_handle.release().handle, + base::GetCurrentProcId()); + base::SharedMemoryHandle ro_handle(ro_platform_handle.release().handle, + base::GetCurrentProcId()); +#else // defined(OS_WIN) + base::SharedMemoryHandle handle(rw_platform_handle.release().handle, false); + base::SharedMemoryHandle ro_handle(ro_platform_handle.release().handle, + false); +#endif // defined(OS_WIN) + + DCHECK(!shared_memory_); + shared_memory_.reset(new base::SharedMemory(handle, false)); + ro_shared_memory_.reset(new base::SharedMemory(ro_handle, true)); + return true; + +#endif // defined(OS_MACOSX) +} + +void PlatformSharedBuffer::InitFromSharedMemoryHandle( + base::SharedMemoryHandle handle) { + DCHECK(!shared_memory_); + + shared_memory_.reset(new base::SharedMemory(handle, read_only_)); +} + +PlatformSharedBufferMapping::~PlatformSharedBufferMapping() { + Unmap(); +} + +void* PlatformSharedBufferMapping::GetBase() const { + return base_; +} + +size_t PlatformSharedBufferMapping::GetLength() const { + return length_; +} + +bool PlatformSharedBufferMapping::Map() { + // Mojo shared buffers can be mapped at any offset. However, + // base::SharedMemory must be mapped at a page boundary. So calculate what the + // nearest whole page offset is, and build a mapping that's offset from that. +#if defined(OS_NACL) + // base::SysInfo isn't available under NaCl. + size_t page_size = getpagesize(); +#else + size_t page_size = base::SysInfo::VMAllocationGranularity(); +#endif + size_t offset_rounding = offset_ % page_size; + size_t real_offset = offset_ - offset_rounding; + size_t real_length = length_ + offset_rounding; + + if (!shared_memory_.MapAt(static_cast(real_offset), real_length)) + return false; + + base_ = static_cast(shared_memory_.memory()) + offset_rounding; + return true; +} + +void PlatformSharedBufferMapping::Unmap() { + shared_memory_.Unmap(); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_shared_buffer.h b/mojo/edk/embedder/platform_shared_buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..45be7233c4f74c5d7e261ad4e058ef5b80b473da --- /dev/null +++ b/mojo/edk/embedder/platform_shared_buffer.h @@ -0,0 +1,178 @@ +// Copyright 2014 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 MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_ + +#include + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/shared_memory.h" +#include "base/memory/shared_memory_handle.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +class PlatformSharedBufferMapping; + +// |PlatformSharedBuffer| is a thread-safe, ref-counted wrapper around +// OS-specific shared memory. It has the following features: +// - A |PlatformSharedBuffer| simply represents a piece of shared memory that +// *may* be mapped and *may* be shared to another process. +// - A single |PlatformSharedBuffer| may be mapped multiple times. The +// lifetime of the mapping (owned by |PlatformSharedBufferMapping|) is +// separate from the lifetime of the |PlatformSharedBuffer|. +// - Sizes/offsets (of the shared memory and mappings) are arbitrary, and not +// restricted by page size. However, more memory may actually be mapped than +// requested. +class MOJO_SYSTEM_IMPL_EXPORT PlatformSharedBuffer + : public base::RefCountedThreadSafe { + public: + // Creates a shared buffer of size |num_bytes| bytes (initially zero-filled). + // |num_bytes| must be nonzero. Returns null on failure. + static PlatformSharedBuffer* Create(size_t num_bytes); + + // Creates a shared buffer of size |num_bytes| from the existing platform + // handle |platform_handle|. Returns null on failure. + static PlatformSharedBuffer* CreateFromPlatformHandle( + size_t num_bytes, + bool read_only, + ScopedPlatformHandle platform_handle); + + // Creates a shared buffer of size |num_bytes| from the existing pair of + // read/write and read-only handles |rw_platform_handle| and + // |ro_platform_handle|. Returns null on failure. + static PlatformSharedBuffer* CreateFromPlatformHandlePair( + size_t num_bytes, + ScopedPlatformHandle rw_platform_handle, + ScopedPlatformHandle ro_platform_handle); + + // Creates a shared buffer of size |num_bytes| from the existing shared memory + // handle |handle|. + static PlatformSharedBuffer* CreateFromSharedMemoryHandle( + size_t num_bytes, + bool read_only, + base::SharedMemoryHandle handle); + + // Gets the size of shared buffer (in number of bytes). + size_t GetNumBytes() const; + + // Returns whether this shared buffer is read-only. + bool IsReadOnly() const; + + // Maps (some) of the shared buffer into memory; [|offset|, |offset + length|] + // must be contained in [0, |num_bytes|], and |length| must be at least 1. + // Returns null on failure. + std::unique_ptr Map(size_t offset, + size_t length); + + // Checks if |offset| and |length| are valid arguments. + bool IsValidMap(size_t offset, size_t length); + + // Like |Map()|, but doesn't check its arguments (which should have been + // preflighted using |IsValidMap()|). + std::unique_ptr MapNoCheck(size_t offset, + size_t length); + + // Duplicates the underlying platform handle and passes it to the caller. + ScopedPlatformHandle DuplicatePlatformHandle(); + + // Duplicates the underlying shared memory handle and passes it to the caller. + base::SharedMemoryHandle DuplicateSharedMemoryHandle(); + + // Passes the underlying platform handle to the caller. This should only be + // called if there's a unique reference to this object (owned by the caller). + // After calling this, this object should no longer be used, but should only + // be disposed of. + ScopedPlatformHandle PassPlatformHandle(); + + // Create and return a read-only duplicate of this shared buffer. If this + // shared buffer isn't capable of returning a read-only duplicate, then + // nullptr will be returned. + PlatformSharedBuffer* CreateReadOnlyDuplicate(); + + private: + friend class base::RefCountedThreadSafe; + + PlatformSharedBuffer(size_t num_bytes, bool read_only); + ~PlatformSharedBuffer(); + + // This is called by |Create()| before this object is given to anyone. + bool Init(); + + // This is like |Init()|, but for |CreateFromPlatformHandle()|. (Note: It + // should verify that |platform_handle| is an appropriate handle for the + // claimed |num_bytes_|.) + bool InitFromPlatformHandle(ScopedPlatformHandle platform_handle); + + bool InitFromPlatformHandlePair(ScopedPlatformHandle rw_platform_handle, + ScopedPlatformHandle ro_platform_handle); + + void InitFromSharedMemoryHandle(base::SharedMemoryHandle handle); + + const size_t num_bytes_; + const bool read_only_; + + base::Lock lock_; + std::unique_ptr shared_memory_; + + // A separate read-only shared memory for platforms that need it (i.e. Linux + // with sync broker). + std::unique_ptr ro_shared_memory_; + + DISALLOW_COPY_AND_ASSIGN(PlatformSharedBuffer); +}; + +// A mapping of a |PlatformSharedBuffer| (compararable to a "file view" in +// Windows); see above. Created by |PlatformSharedBuffer::Map()|. Automatically +// unmaps memory on destruction. +// +// Mappings are NOT thread-safe. +// +// Note: This is an entirely separate class (instead of +// |PlatformSharedBuffer::Mapping|) so that it can be forward-declared. +class MOJO_SYSTEM_IMPL_EXPORT PlatformSharedBufferMapping { + public: + ~PlatformSharedBufferMapping(); + + void* GetBase() const; + size_t GetLength() const; + + private: + friend class PlatformSharedBuffer; + + PlatformSharedBufferMapping(base::SharedMemoryHandle handle, + bool read_only, + size_t offset, + size_t length) + : offset_(offset), + length_(length), + base_(nullptr), + shared_memory_(handle, read_only) {} + + bool Map(); + void Unmap(); + + const size_t offset_; + const size_t length_; + void* base_; + + // Since mapping life cycles are separate from PlatformSharedBuffer and a + // buffer can be mapped multiple times, we have our own SharedMemory object + // created from a duplicate handle. + base::SharedMemory shared_memory_; + + DISALLOW_COPY_AND_ASSIGN(PlatformSharedBufferMapping); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_ diff --git a/mojo/edk/embedder/platform_shared_buffer_unittest.cc b/mojo/edk/embedder/platform_shared_buffer_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..f1593f06c448f7ceec3d12d7d44b196659149cd4 --- /dev/null +++ b/mojo/edk/embedder/platform_shared_buffer_unittest.cc @@ -0,0 +1,227 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/platform_shared_buffer.h" + +#include + +#include +#include + +#include "base/memory/ref_counted.h" +#include "base/memory/shared_memory.h" +#include "base/sys_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#endif + +namespace mojo { +namespace edk { +namespace { + +TEST(PlatformSharedBufferTest, Basic) { + const size_t kNumInts = 100; + const size_t kNumBytes = kNumInts * sizeof(int); + // A fudge so that we're not just writing zero bytes 75% of the time. + const int kFudge = 1234567890; + + // Make some memory. + scoped_refptr buffer( + PlatformSharedBuffer::Create(kNumBytes)); + ASSERT_TRUE(buffer); + + // Map it all, scribble some stuff, and then unmap it. + { + EXPECT_TRUE(buffer->IsValidMap(0, kNumBytes)); + std::unique_ptr mapping( + buffer->Map(0, kNumBytes)); + ASSERT_TRUE(mapping); + ASSERT_TRUE(mapping->GetBase()); + int* stuff = static_cast(mapping->GetBase()); + for (size_t i = 0; i < kNumInts; i++) + stuff[i] = static_cast(i) + kFudge; + } + + // Map it all again, check that our scribbling is still there, then do a + // partial mapping and scribble on that, check that everything is coherent, + // unmap the first mapping, scribble on some of the second mapping, and then + // unmap it. + { + ASSERT_TRUE(buffer->IsValidMap(0, kNumBytes)); + // Use |MapNoCheck()| this time. + std::unique_ptr mapping1( + buffer->MapNoCheck(0, kNumBytes)); + ASSERT_TRUE(mapping1); + ASSERT_TRUE(mapping1->GetBase()); + int* stuff1 = static_cast(mapping1->GetBase()); + for (size_t i = 0; i < kNumInts; i++) + EXPECT_EQ(static_cast(i) + kFudge, stuff1[i]) << i; + + std::unique_ptr mapping2( + buffer->Map((kNumInts / 2) * sizeof(int), 2 * sizeof(int))); + ASSERT_TRUE(mapping2); + ASSERT_TRUE(mapping2->GetBase()); + int* stuff2 = static_cast(mapping2->GetBase()); + EXPECT_EQ(static_cast(kNumInts / 2) + kFudge, stuff2[0]); + EXPECT_EQ(static_cast(kNumInts / 2) + 1 + kFudge, stuff2[1]); + + stuff2[0] = 123; + stuff2[1] = 456; + EXPECT_EQ(123, stuff1[kNumInts / 2]); + EXPECT_EQ(456, stuff1[kNumInts / 2 + 1]); + + mapping1.reset(); + + EXPECT_EQ(123, stuff2[0]); + EXPECT_EQ(456, stuff2[1]); + stuff2[1] = 789; + } + + // Do another partial mapping and check that everything is the way we expect + // it to be. + { + EXPECT_TRUE(buffer->IsValidMap(sizeof(int), kNumBytes - sizeof(int))); + std::unique_ptr mapping( + buffer->Map(sizeof(int), kNumBytes - sizeof(int))); + ASSERT_TRUE(mapping); + ASSERT_TRUE(mapping->GetBase()); + int* stuff = static_cast(mapping->GetBase()); + + for (size_t j = 0; j < kNumInts - 1; j++) { + int i = static_cast(j) + 1; + if (i == kNumInts / 2) { + EXPECT_EQ(123, stuff[j]); + } else if (i == kNumInts / 2 + 1) { + EXPECT_EQ(789, stuff[j]); + } else { + EXPECT_EQ(i + kFudge, stuff[j]) << i; + } + } + } +} + +// TODO(vtl): Bigger buffers. + +TEST(PlatformSharedBufferTest, InvalidMappings) { + scoped_refptr buffer(PlatformSharedBuffer::Create(100)); + ASSERT_TRUE(buffer); + + // Zero length not allowed. + EXPECT_FALSE(buffer->Map(0, 0)); + EXPECT_FALSE(buffer->IsValidMap(0, 0)); + + // Okay: + EXPECT_TRUE(buffer->Map(0, 100)); + EXPECT_TRUE(buffer->IsValidMap(0, 100)); + // Offset + length too big. + EXPECT_FALSE(buffer->Map(0, 101)); + EXPECT_FALSE(buffer->IsValidMap(0, 101)); + EXPECT_FALSE(buffer->Map(1, 100)); + EXPECT_FALSE(buffer->IsValidMap(1, 100)); + + // Okay: + EXPECT_TRUE(buffer->Map(50, 50)); + EXPECT_TRUE(buffer->IsValidMap(50, 50)); + // Offset + length too big. + EXPECT_FALSE(buffer->Map(50, 51)); + EXPECT_FALSE(buffer->IsValidMap(50, 51)); + EXPECT_FALSE(buffer->Map(51, 50)); + EXPECT_FALSE(buffer->IsValidMap(51, 50)); +} + +TEST(PlatformSharedBufferTest, TooBig) { + // If |size_t| is 32-bit, it's quite possible/likely that |Create()| succeeds + // (since it only involves creating a 4 GB file). + size_t max_size = std::numeric_limits::max(); + if (base::SysInfo::AmountOfVirtualMemory() && + max_size > static_cast(base::SysInfo::AmountOfVirtualMemory())) + max_size = static_cast(base::SysInfo::AmountOfVirtualMemory()); + scoped_refptr buffer( + PlatformSharedBuffer::Create(max_size)); + // But, assuming |sizeof(size_t) == sizeof(void*)|, mapping all of it should + // always fail. + if (buffer) + EXPECT_FALSE(buffer->Map(0, max_size)); +} + +// Tests that separate mappings get distinct addresses. +// Note: It's not inconceivable that the OS could ref-count identical mappings +// and reuse the same address, in which case we'd have to be more careful about +// using the address as the key for unmapping. +TEST(PlatformSharedBufferTest, MappingsDistinct) { + scoped_refptr buffer(PlatformSharedBuffer::Create(100)); + std::unique_ptr mapping1(buffer->Map(0, 100)); + std::unique_ptr mapping2(buffer->Map(0, 100)); + EXPECT_NE(mapping1->GetBase(), mapping2->GetBase()); +} + +TEST(PlatformSharedBufferTest, BufferZeroInitialized) { + static const size_t kSizes[] = {10, 100, 1000, 10000, 100000}; + for (size_t i = 0; i < arraysize(kSizes); i++) { + scoped_refptr buffer( + PlatformSharedBuffer::Create(kSizes[i])); + std::unique_ptr mapping( + buffer->Map(0, kSizes[i])); + for (size_t j = 0; j < kSizes[i]; j++) { + // "Assert" instead of "expect" so we don't spam the output with thousands + // of failures if we fail. + ASSERT_EQ('\0', static_cast(mapping->GetBase())[j]) + << "size " << kSizes[i] << ", offset " << j; + } + } +} + +TEST(PlatformSharedBufferTest, MappingsOutliveBuffer) { + std::unique_ptr mapping1; + std::unique_ptr mapping2; + + { + scoped_refptr buffer( + PlatformSharedBuffer::Create(100)); + mapping1 = buffer->Map(0, 100); + mapping2 = buffer->Map(50, 50); + static_cast(mapping1->GetBase())[50] = 'x'; + } + + EXPECT_EQ('x', static_cast(mapping2->GetBase())[0]); + + static_cast(mapping2->GetBase())[1] = 'y'; + EXPECT_EQ('y', static_cast(mapping1->GetBase())[51]); +} + +TEST(PlatformSharedBufferTest, FromSharedMemoryHandle) { + const size_t kBufferSize = 1234; + base::SharedMemoryCreateOptions options; + options.size = kBufferSize; + base::SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.Create(options)); + ASSERT_TRUE(shared_memory.Map(kBufferSize)); + + base::SharedMemoryHandle shm_handle = + base::SharedMemory::DuplicateHandle(shared_memory.handle()); + scoped_refptr simple_buffer( + PlatformSharedBuffer::CreateFromSharedMemoryHandle( + kBufferSize, false /* read_only */, shm_handle)); + ASSERT_TRUE(simple_buffer); + + std::unique_ptr mapping = + simple_buffer->Map(0, kBufferSize); + ASSERT_TRUE(mapping); + + const int kOffset = 123; + char* base_memory = static_cast(shared_memory.memory()); + char* mojo_memory = static_cast(mapping->GetBase()); + base_memory[kOffset] = 0; + EXPECT_EQ(0, mojo_memory[kOffset]); + base_memory[kOffset] = 'a'; + EXPECT_EQ('a', mojo_memory[kOffset]); + mojo_memory[kOffset] = 'z'; + EXPECT_EQ('z', base_memory[kOffset]); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/scoped_ipc_support.cc b/mojo/edk/embedder/scoped_ipc_support.cc new file mode 100644 index 0000000000000000000000000000000000000000..f67210a81775edec8744299ee86ad93f31554017 --- /dev/null +++ b/mojo/edk/embedder/scoped_ipc_support.cc @@ -0,0 +1,39 @@ +// 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. + +#include "mojo/edk/embedder/scoped_ipc_support.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_restrictions.h" +#include "mojo/edk/embedder/embedder.h" + +namespace mojo { +namespace edk { + +ScopedIPCSupport::ScopedIPCSupport( + scoped_refptr io_thread_task_runner, + ShutdownPolicy shutdown_policy) : shutdown_policy_(shutdown_policy) { + InitIPCSupport(io_thread_task_runner); +} + +ScopedIPCSupport::~ScopedIPCSupport() { + if (shutdown_policy_ == ShutdownPolicy::FAST) { + ShutdownIPCSupport(base::Bind(&base::DoNothing)); + return; + } + + base::WaitableEvent shutdown_event( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + ShutdownIPCSupport(base::Bind(&base::WaitableEvent::Signal, + base::Unretained(&shutdown_event))); + + base::ThreadRestrictions::ScopedAllowWait allow_io; + shutdown_event.Wait(); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/scoped_ipc_support.h b/mojo/edk/embedder/scoped_ipc_support.h new file mode 100644 index 0000000000000000000000000000000000000000..22d8e501139e39a56a375caae2c7bfbff679f33b --- /dev/null +++ b/mojo/edk/embedder/scoped_ipc_support.h @@ -0,0 +1,118 @@ +// 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. + +#ifndef MOJO_EDK_EMBEDDER_SCOPED_IPC_SUPPORT_H_ +#define MOJO_EDK_EMBEDDER_SCOPED_IPC_SUPPORT_H_ + +#include "base/memory/ref_counted.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace base { +class TaskRunner; +} + +namespace mojo { +namespace edk { + +// A simple class that calls |InitIPCSupport()| on construction and +// |ShutdownIPCSupport()| on destruction, blocking the destructor on clean IPC +// shutdown completion. +class MOJO_SYSTEM_IMPL_EXPORT ScopedIPCSupport { + public: + // ShutdownPolicy is a type for specifying the desired Mojo IPC support + // shutdown behavior used during ScopedIPCSupport destruction. + // + // What follows is a quick overview of why shutdown behavior is interesting + // and how you might decide which behavior is right for your use case. + // + // BACKGROUND + // ========== + // + // In order to facilitate efficient and reliable transfer of Mojo message pipe + // endpoints across process boundaries, the underlying model for a message + // pipe is actually a self-collapsing cycle of "ports." See + // //mojo/edk/system/ports for gritty implementation details. + // + // Ports are essentially globally unique identifiers used for system-wide + // message routing. Every message pipe consists of at least two such ports: + // the pipe's two concrete endpoints. + // + // When a message pipe endpoint is transferred over another message pipe, that + // endpoint's port (which subsequently exists only internally with no + // publicly-reachable handle) enters a transient proxying state for the + // remainder of its lifetime. Once sufficient information has been + // proagated throughout the system and this proxying port can be safely + // bypassed, it is garbage-collected. + // + // If a process is terminated while hosting any active proxy ports, this + // will necessarily break the message pipe(s) to which those ports belong. + // + // WHEN TO USE CLEAN SHUTDOWN + // ========================== + // + // Consider three processes, A, B, and C. Suppose A creates a message pipe, + // sending one end to B and the other to C. For some brief period of time, + // messages sent by B or C over this pipe may be proxied through A. + // + // If A is suddenly terminated, there may be no way for B's messages to reach + // C (and vice versa), since the message pipe state may not have been fully + // propagated to all concerned processes in the system. As such, both B and C + // may have no choice but to signal peer closure on their respective ends of + // the pipe, and thus the pipe may be broken despite a lack of intent by + // either B or C. + // + // This can also happen if A creates a pipe and passes one end to B, who then + // passes it along to C. B may temporarily proxy messages for this pipe + // between A and C, and B's sudden demise will in turn beget the pipe's + // own sudden demise. + // + // In situations where these sort of arrangements may occur, potentially + // proxying processes must ensure they are shut down cleanly in order to avoid + // flaky system behavior. + // + // WHEN TO USE FAST SHUTDOWN + // ========================= + // + // As a general rule of thumb, if your process never creates a message pipe + // where both ends are passed to other processes, or never forwards a pipe + // endpoint from one process to another, fast shutdown is safe. Satisfaction + // of these constraints can be difficult to prove though, so clean shutdown is + // a safe default choice. + // + // Content renderer processes are a good example of a case where fast shutdown + // is safe, because as a matter of security and stability, a renderer cannot + // be trusted to do any proxying on behalf of two other processes anyway. + // + // There are other practical scenarios where fast shutdown is safe even if + // the process may have live proxies. For example, content's browser process + // is treated as a sort of master process in the system, in the sense that if + // the browser is terminated, no other part of the system is expected to + // continue normal operation anyway. In this case the side-effects of fast + // shutdown are irrelevant, so fast shutdown is preferred. + enum class ShutdownPolicy { + // Clean shutdown. This causes the ScopedIPCSupport destructor to *block* + // the calling thread until clean shutdown is complete. See explanation + // above for details. + CLEAN, + + // Fast shutdown. In this case a cheap best-effort attempt is made to + // shut down the IPC system, but no effort is made to wait for its + // completion. See explanation above for details. + FAST, + }; + + ScopedIPCSupport(scoped_refptr io_thread_task_runner, + ShutdownPolicy shutdown_policy); + ~ScopedIPCSupport(); + + private: + const ShutdownPolicy shutdown_policy_; + + DISALLOW_COPY_AND_ASSIGN(ScopedIPCSupport); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_SCOPED_IPC_SUPPORT_H_ diff --git a/mojo/edk/embedder/scoped_platform_handle.h b/mojo/edk/embedder/scoped_platform_handle.h new file mode 100644 index 0000000000000000000000000000000000000000..15b80ea4b288c014373a7dc8d66566e8b4c837c7 --- /dev/null +++ b/mojo/edk/embedder/scoped_platform_handle.h @@ -0,0 +1,63 @@ +// Copyright 2014 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 MOJO_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_ +#define MOJO_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/macros.h" + +namespace mojo { +namespace edk { + +class MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle { + public: + ScopedPlatformHandle() {} + explicit ScopedPlatformHandle(PlatformHandle handle) : handle_(handle) {} + ~ScopedPlatformHandle() { handle_.CloseIfNecessary(); } + + // Move-only constructor and operator=. + ScopedPlatformHandle(ScopedPlatformHandle&& other) + : handle_(other.release()) {} + + ScopedPlatformHandle& operator=(ScopedPlatformHandle&& other) { + if (this != &other) + handle_ = other.release(); + return *this; + } + + const PlatformHandle& get() const { return handle_; } + + void swap(ScopedPlatformHandle& other) { + PlatformHandle temp = handle_; + handle_ = other.handle_; + other.handle_ = temp; + } + + PlatformHandle release() WARN_UNUSED_RESULT { + PlatformHandle rv = handle_; + handle_ = PlatformHandle(); + return rv; + } + + void reset(PlatformHandle handle = PlatformHandle()) { + handle_.CloseIfNecessary(); + handle_ = handle; + } + + bool is_valid() const { return handle_.is_valid(); } + + private: + PlatformHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPlatformHandle); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_ diff --git a/mojo/edk/embedder/test_embedder.cc b/mojo/edk/embedder/test_embedder.cc new file mode 100644 index 0000000000000000000000000000000000000000..9658010586fc342c9ec4f6641dc5885f85ed9bc5 --- /dev/null +++ b/mojo/edk/embedder/test_embedder.cc @@ -0,0 +1,46 @@ +// Copyright 2014 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. + +#include "mojo/edk/embedder/test_embedder.h" + +#include + +#include "base/logging.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/handle_table.h" + +namespace mojo { + +namespace edk { +namespace internal { + +bool ShutdownCheckNoLeaks(Core* core) { + std::vector leaked_handles; + core->GetActiveHandlesForTest(&leaked_handles); + if (leaked_handles.empty()) + return true; + for (auto handle : leaked_handles) + LOG(ERROR) << "Mojo embedder shutdown: Leaking handle " << handle; + return false; +} + +} // namespace internal + +namespace test { + +bool Shutdown() { + CHECK(internal::g_core); + bool rv = internal::ShutdownCheckNoLeaks(internal::g_core); + delete internal::g_core; + internal::g_core = nullptr; + + return rv; +} + +} // namespace test +} // namespace edk + +} // namespace mojo diff --git a/mojo/edk/embedder/test_embedder.h b/mojo/edk/embedder/test_embedder.h new file mode 100644 index 0000000000000000000000000000000000000000..c64ba179e1de0af5fb76975cb6c14cabd83e6766 --- /dev/null +++ b/mojo/edk/embedder/test_embedder.h @@ -0,0 +1,28 @@ +// Copyright 2014 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 MOJO_EDK_EMBEDDER_TEST_EMBEDDER_H_ +#define MOJO_EDK_EMBEDDER_TEST_EMBEDDER_H_ + +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { +namespace test { + +// This shuts down the global, singleton instance. (Note: "Real" embedders are +// not expected to ever shut down this instance. This |Shutdown()| function will +// do more work to ensure that tests don't leak, etc.) Returns true if there +// were no problems, false if there were leaks -- i.e., handles still open -- or +// any other problems. +// +// Note: It is up to the caller to ensure that there are not outstanding +// callbacks from |CreateChannel()| before calling this. +MOJO_SYSTEM_IMPL_EXPORT bool Shutdown(); + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_TEST_EMBEDDER_H_ diff --git a/mojo/edk/js/BUILD.gn b/mojo/edk/js/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..fc1e03c3a44cc791329a168121ebc1e8067c58bd --- /dev/null +++ b/mojo/edk/js/BUILD.gn @@ -0,0 +1,35 @@ +# Copyright 2014 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. + +component("js") { + sources = [ + "core.cc", + "core.h", + "drain_data.cc", + "drain_data.h", + "handle.cc", + "handle.h", + "handle_close_observer.h", + "js_export.h", + "mojo_runner_delegate.cc", + "mojo_runner_delegate.h", + "support.cc", + "support.h", + "threading.cc", + "threading.h", + "waiting_callback.cc", + "waiting_callback.h", + ] + + public_deps = [ + "//base", + "//gin", + "//v8", + ] + + deps = [ + "//mojo/public/cpp/system", + ] + defines = [ "MOJO_JS_IMPLEMENTATION" ] +} diff --git a/mojo/edk/js/core.cc b/mojo/edk/js/core.cc new file mode 100644 index 0000000000000000000000000000000000000000..baccc4c0d72f304098cad588deb6b2d380362a42 --- /dev/null +++ b/mojo/edk/js/core.cc @@ -0,0 +1,454 @@ +// Copyright 2014 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. + +#include "mojo/edk/js/core.h" + +#include +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "gin/arguments.h" +#include "gin/array_buffer.h" +#include "gin/converter.h" +#include "gin/dictionary.h" +#include "gin/function_template.h" +#include "gin/handle.h" +#include "gin/object_template_builder.h" +#include "gin/per_isolate_data.h" +#include "gin/public/wrapper_info.h" +#include "gin/wrappable.h" +#include "mojo/edk/js/drain_data.h" +#include "mojo/edk/js/handle.h" +#include "mojo/public/cpp/system/wait.h" + +namespace mojo { +namespace edk { +namespace js { + +namespace { + +MojoResult CloseHandle(gin::Handle handle) { + if (!handle->get().is_valid()) + return MOJO_RESULT_INVALID_ARGUMENT; + handle->Close(); + return MOJO_RESULT_OK; +} + +gin::Dictionary QueryHandleSignalsState(const gin::Arguments& args, + mojo::Handle handle) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + if (!handle.is_valid()) { + dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT); + } else { + HandleSignalsState state = handle.QuerySignalsState(); + dictionary.Set("result", MOJO_RESULT_OK); + dictionary.Set("satisfiedSignals", state.satisfied_signals); + dictionary.Set("satisfiableSignals", state.satisfiable_signals); + } + return dictionary; +} + +gin::Dictionary WaitHandle(const gin::Arguments& args, + mojo::Handle handle, + MojoHandleSignals signals) { + v8::Isolate* isolate = args.isolate(); + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate); + + MojoHandleSignalsState signals_state; + MojoResult result = Wait(handle, signals, &signals_state); + dictionary.Set("result", result); + + if (result != MOJO_RESULT_OK && result != MOJO_RESULT_FAILED_PRECONDITION) { + dictionary.Set("signalsState", v8::Null(isolate).As()); + } else { + gin::Dictionary signalsStateDict = gin::Dictionary::CreateEmpty(isolate); + signalsStateDict.Set("satisfiedSignals", signals_state.satisfied_signals); + signalsStateDict.Set("satisfiableSignals", + signals_state.satisfiable_signals); + dictionary.Set("signalsState", signalsStateDict); + } + + return dictionary; +} + +gin::Dictionary CreateMessagePipe(const gin::Arguments& args) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT); + + MojoHandle handle0 = MOJO_HANDLE_INVALID; + MojoHandle handle1 = MOJO_HANDLE_INVALID; + MojoResult result = MOJO_RESULT_OK; + + v8::Handle options_value = args.PeekNext(); + if (options_value.IsEmpty() || options_value->IsNull() || + options_value->IsUndefined()) { + result = MojoCreateMessagePipe(NULL, &handle0, &handle1); + } else if (options_value->IsObject()) { + gin::Dictionary options_dict(args.isolate(), options_value->ToObject()); + MojoCreateMessagePipeOptions options; + // For future struct_size, we can probably infer that from the presence of + // properties in options_dict. For now, it's always 8. + options.struct_size = 8; + // Ideally these would be optional. But the interface makes it hard to + // typecheck them then. + if (!options_dict.Get("flags", &options.flags)) { + return dictionary; + } + + result = MojoCreateMessagePipe(&options, &handle0, &handle1); + } else { + return dictionary; + } + + CHECK_EQ(MOJO_RESULT_OK, result); + + dictionary.Set("result", result); + dictionary.Set("handle0", mojo::Handle(handle0)); + dictionary.Set("handle1", mojo::Handle(handle1)); + return dictionary; +} + +MojoResult WriteMessage( + mojo::Handle handle, + const gin::ArrayBufferView& buffer, + const std::vector >& handles, + MojoWriteMessageFlags flags) { + std::vector raw_handles(handles.size()); + for (size_t i = 0; i < handles.size(); ++i) + raw_handles[i] = handles[i]->get().value(); + MojoResult rv = MojoWriteMessage(handle.value(), + buffer.bytes(), + static_cast(buffer.num_bytes()), + raw_handles.empty() ? NULL : &raw_handles[0], + static_cast(raw_handles.size()), + flags); + // MojoWriteMessage takes ownership of the handles, so release them here. + for (size_t i = 0; i < handles.size(); ++i) + ignore_result(handles[i]->release()); + + return rv; +} + +gin::Dictionary ReadMessage(const gin::Arguments& args, + mojo::Handle handle, + MojoReadMessageFlags flags) { + uint32_t num_bytes = 0; + uint32_t num_handles = 0; + MojoResult result = MojoReadMessage( + handle.value(), NULL, &num_bytes, NULL, &num_handles, flags); + if (result != MOJO_RESULT_RESOURCE_EXHAUSTED) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", result); + return dictionary; + } + + v8::Handle array_buffer = + v8::ArrayBuffer::New(args.isolate(), num_bytes); + std::vector handles(num_handles); + + gin::ArrayBuffer buffer; + ConvertFromV8(args.isolate(), array_buffer, &buffer); + CHECK(buffer.num_bytes() == num_bytes); + + result = MojoReadMessage(handle.value(), + buffer.bytes(), + &num_bytes, + handles.empty() ? NULL : + reinterpret_cast(&handles[0]), + &num_handles, + flags); + + CHECK(buffer.num_bytes() == num_bytes); + CHECK(handles.size() == num_handles); + + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", result); + dictionary.Set("buffer", array_buffer); + dictionary.Set("handles", handles); + return dictionary; +} + +gin::Dictionary CreateDataPipe(const gin::Arguments& args) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT); + + MojoHandle producer_handle = MOJO_HANDLE_INVALID; + MojoHandle consumer_handle = MOJO_HANDLE_INVALID; + MojoResult result = MOJO_RESULT_OK; + + v8::Handle options_value = args.PeekNext(); + if (options_value.IsEmpty() || options_value->IsNull() || + options_value->IsUndefined()) { + result = MojoCreateDataPipe(NULL, &producer_handle, &consumer_handle); + } else if (options_value->IsObject()) { + gin::Dictionary options_dict(args.isolate(), options_value->ToObject()); + MojoCreateDataPipeOptions options; + // For future struct_size, we can probably infer that from the presence of + // properties in options_dict. For now, it's always 16. + options.struct_size = 16; + // Ideally these would be optional. But the interface makes it hard to + // typecheck them then. + if (!options_dict.Get("flags", &options.flags) || + !options_dict.Get("elementNumBytes", &options.element_num_bytes) || + !options_dict.Get("capacityNumBytes", &options.capacity_num_bytes)) { + return dictionary; + } + + result = MojoCreateDataPipe(&options, &producer_handle, &consumer_handle); + } else { + return dictionary; + } + + CHECK_EQ(MOJO_RESULT_OK, result); + + dictionary.Set("result", result); + dictionary.Set("producerHandle", mojo::Handle(producer_handle)); + dictionary.Set("consumerHandle", mojo::Handle(consumer_handle)); + return dictionary; +} + +gin::Dictionary WriteData(const gin::Arguments& args, + mojo::Handle handle, + const gin::ArrayBufferView& buffer, + MojoWriteDataFlags flags) { + uint32_t num_bytes = static_cast(buffer.num_bytes()); + MojoResult result = + MojoWriteData(handle.value(), buffer.bytes(), &num_bytes, flags); + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", result); + dictionary.Set("numBytes", num_bytes); + return dictionary; +} + +gin::Dictionary ReadData(const gin::Arguments& args, + mojo::Handle handle, + MojoReadDataFlags flags) { + uint32_t num_bytes = 0; + MojoResult result = MojoReadData( + handle.value(), NULL, &num_bytes, MOJO_READ_DATA_FLAG_QUERY); + if (result != MOJO_RESULT_OK) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", result); + return dictionary; + } + + v8::Handle array_buffer = + v8::ArrayBuffer::New(args.isolate(), num_bytes); + gin::ArrayBuffer buffer; + ConvertFromV8(args.isolate(), array_buffer, &buffer); + CHECK_EQ(num_bytes, buffer.num_bytes()); + + result = MojoReadData(handle.value(), buffer.bytes(), &num_bytes, flags); + CHECK_EQ(num_bytes, buffer.num_bytes()); + + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", result); + dictionary.Set("buffer", array_buffer); + return dictionary; +} + +// Asynchronously read all of the data available for the specified data pipe +// consumer handle until the remote handle is closed or an error occurs. A +// Promise is returned whose settled value is an object like this: +// {result: core.RESULT_OK, buffer: dataArrayBuffer}. If the read failed, +// then the Promise is rejected, the result will be the actual error code, +// and the buffer will contain whatever was read before the error occurred. +// The drainData data pipe handle argument is closed automatically. + +v8::Handle DoDrainData(gin::Arguments* args, + gin::Handle handle) { + return (new DrainData(args->isolate(), handle->release()))->GetPromise(); +} + +bool IsHandle(gin::Arguments* args, v8::Handle val) { + gin::Handle ignore_handle; + return gin::Converter>::FromV8( + args->isolate(), val, &ignore_handle); +} + +gin::Dictionary CreateSharedBuffer(const gin::Arguments& args, + uint64_t num_bytes, + MojoCreateSharedBufferOptionsFlags flags) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + MojoHandle handle = MOJO_HANDLE_INVALID; + MojoCreateSharedBufferOptions options; + // The |flags| is mandatory parameter for CreateSharedBuffer, and it will + // be always initialized in MojoCreateSharedBufferOptions struct. For + // forward compatibility, set struct_size to be 8 bytes (struct_size + flags), + // so that validator will only check the field that is set. + options.struct_size = 8; + options.flags = flags; + MojoResult result = MojoCreateSharedBuffer(&options, num_bytes, &handle); + if (result != MOJO_RESULT_OK) { + dictionary.Set("result", result); + return dictionary; + } + + dictionary.Set("result", result); + dictionary.Set("handle", mojo::Handle(handle)); + + return dictionary; +} + +gin::Dictionary DuplicateBufferHandle( + const gin::Arguments& args, + mojo::Handle handle, + MojoDuplicateBufferHandleOptionsFlags flags) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + MojoHandle duped = MOJO_HANDLE_INVALID; + MojoDuplicateBufferHandleOptions options; + // The |flags| is mandatory parameter for DuplicateBufferHandle, and it will + // be always initialized in MojoDuplicateBufferHandleOptions struct. For + // forward compatibility, set struct_size to be 8 bytes (struct_size + flags), + // so that validator will only check the field that is set. + options.struct_size = 8; + options.flags = flags; + MojoResult result = + MojoDuplicateBufferHandle(handle.value(), &options, &duped); + if (result != MOJO_RESULT_OK) { + dictionary.Set("result", result); + return dictionary; + } + + dictionary.Set("result", result); + dictionary.Set("handle", mojo::Handle(duped)); + + return dictionary; +} + +gin::Dictionary MapBuffer(const gin::Arguments& args, + mojo::Handle handle, + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + void* data = nullptr; + MojoResult result = + MojoMapBuffer(handle.value(), offset, num_bytes, &data, flags); + if (result != MOJO_RESULT_OK) { + dictionary.Set("result", result); + return dictionary; + } + + v8::Handle array_buffer = + v8::ArrayBuffer::New(args.isolate(), data, num_bytes); + + dictionary.Set("result", result); + dictionary.Set("buffer", array_buffer); + + return dictionary; +} + +MojoResult UnmapBuffer(const gin::Arguments& args, + const v8::Handle& buffer) { + // Buffer must be external, created by MapBuffer + if (!buffer->IsExternal()) + return MOJO_RESULT_INVALID_ARGUMENT; + + return MojoUnmapBuffer(buffer->GetContents().Data()); +} + +gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin }; + +} // namespace + +const char Core::kModuleName[] = "mojo/public/js/core"; + +v8::Local Core::GetModule(v8::Isolate* isolate) { + gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); + v8::Local templ = data->GetObjectTemplate( + &g_wrapper_info); + + if (templ.IsEmpty()) { + templ = + gin::ObjectTemplateBuilder(isolate) + // TODO(mpcomplete): Should these just be methods on the JS Handle + // object? + .SetMethod("close", CloseHandle) + .SetMethod("queryHandleSignalsState", QueryHandleSignalsState) + .SetMethod("wait", WaitHandle) + .SetMethod("createMessagePipe", CreateMessagePipe) + .SetMethod("writeMessage", WriteMessage) + .SetMethod("readMessage", ReadMessage) + .SetMethod("createDataPipe", CreateDataPipe) + .SetMethod("writeData", WriteData) + .SetMethod("readData", ReadData) + .SetMethod("drainData", DoDrainData) + .SetMethod("isHandle", IsHandle) + .SetMethod("createSharedBuffer", CreateSharedBuffer) + .SetMethod("duplicateBufferHandle", DuplicateBufferHandle) + .SetMethod("mapBuffer", MapBuffer) + .SetMethod("unmapBuffer", UnmapBuffer) + + .SetValue("RESULT_OK", MOJO_RESULT_OK) + .SetValue("RESULT_CANCELLED", MOJO_RESULT_CANCELLED) + .SetValue("RESULT_UNKNOWN", MOJO_RESULT_UNKNOWN) + .SetValue("RESULT_INVALID_ARGUMENT", MOJO_RESULT_INVALID_ARGUMENT) + .SetValue("RESULT_DEADLINE_EXCEEDED", MOJO_RESULT_DEADLINE_EXCEEDED) + .SetValue("RESULT_NOT_FOUND", MOJO_RESULT_NOT_FOUND) + .SetValue("RESULT_ALREADY_EXISTS", MOJO_RESULT_ALREADY_EXISTS) + .SetValue("RESULT_PERMISSION_DENIED", MOJO_RESULT_PERMISSION_DENIED) + .SetValue("RESULT_RESOURCE_EXHAUSTED", + MOJO_RESULT_RESOURCE_EXHAUSTED) + .SetValue("RESULT_FAILED_PRECONDITION", + MOJO_RESULT_FAILED_PRECONDITION) + .SetValue("RESULT_ABORTED", MOJO_RESULT_ABORTED) + .SetValue("RESULT_OUT_OF_RANGE", MOJO_RESULT_OUT_OF_RANGE) + .SetValue("RESULT_UNIMPLEMENTED", MOJO_RESULT_UNIMPLEMENTED) + .SetValue("RESULT_INTERNAL", MOJO_RESULT_INTERNAL) + .SetValue("RESULT_UNAVAILABLE", MOJO_RESULT_UNAVAILABLE) + .SetValue("RESULT_DATA_LOSS", MOJO_RESULT_DATA_LOSS) + .SetValue("RESULT_BUSY", MOJO_RESULT_BUSY) + .SetValue("RESULT_SHOULD_WAIT", MOJO_RESULT_SHOULD_WAIT) + + .SetValue("HANDLE_SIGNAL_NONE", MOJO_HANDLE_SIGNAL_NONE) + .SetValue("HANDLE_SIGNAL_READABLE", MOJO_HANDLE_SIGNAL_READABLE) + .SetValue("HANDLE_SIGNAL_WRITABLE", MOJO_HANDLE_SIGNAL_WRITABLE) + .SetValue("HANDLE_SIGNAL_PEER_CLOSED", + MOJO_HANDLE_SIGNAL_PEER_CLOSED) + + .SetValue("CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE", + MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE) + + .SetValue("WRITE_MESSAGE_FLAG_NONE", MOJO_WRITE_MESSAGE_FLAG_NONE) + + .SetValue("READ_MESSAGE_FLAG_NONE", MOJO_READ_MESSAGE_FLAG_NONE) + .SetValue("READ_MESSAGE_FLAG_MAY_DISCARD", + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD) + + .SetValue("CREATE_DATA_PIPE_OPTIONS_FLAG_NONE", + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE) + + .SetValue("WRITE_DATA_FLAG_NONE", MOJO_WRITE_DATA_FLAG_NONE) + .SetValue("WRITE_DATA_FLAG_ALL_OR_NONE", + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE) + + .SetValue("READ_DATA_FLAG_NONE", MOJO_READ_DATA_FLAG_NONE) + .SetValue("READ_DATA_FLAG_ALL_OR_NONE", + MOJO_READ_DATA_FLAG_ALL_OR_NONE) + .SetValue("READ_DATA_FLAG_DISCARD", MOJO_READ_DATA_FLAG_DISCARD) + .SetValue("READ_DATA_FLAG_QUERY", MOJO_READ_DATA_FLAG_QUERY) + .SetValue("READ_DATA_FLAG_PEEK", MOJO_READ_DATA_FLAG_PEEK) + .SetValue("CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE", + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE) + + .SetValue("DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE", + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE) + + .SetValue("DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY", + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY) + + .SetValue("MAP_BUFFER_FLAG_NONE", MOJO_MAP_BUFFER_FLAG_NONE) + .Build(); + + data->SetObjectTemplate(&g_wrapper_info, templ); + } + + return templ->NewInstance(); +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/core.h b/mojo/edk/js/core.h new file mode 100644 index 0000000000000000000000000000000000000000..97ef5dfd8118e73408371971ef008854e9757a65 --- /dev/null +++ b/mojo/edk/js/core.h @@ -0,0 +1,25 @@ +// Copyright 2014 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 MOJO_EDK_JS_CORE_H_ +#define MOJO_EDK_JS_CORE_H_ + +#include "mojo/edk/js/js_export.h" +#include "v8/include/v8.h" + +namespace mojo { +namespace edk { +namespace js { + +class MOJO_JS_EXPORT Core { + public: + static const char kModuleName[]; + static v8::Local GetModule(v8::Isolate* isolate); +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_CORE_H_ diff --git a/mojo/edk/js/drain_data.cc b/mojo/edk/js/drain_data.cc new file mode 100644 index 0000000000000000000000000000000000000000..334ced32e759e9a69b2a221676aa7d67a51bb65f --- /dev/null +++ b/mojo/edk/js/drain_data.cc @@ -0,0 +1,129 @@ +// Copyright 2014 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. + +#include "mojo/edk/js/drain_data.h" + +#include +#include + +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "gin/array_buffer.h" +#include "gin/converter.h" +#include "gin/dictionary.h" +#include "gin/per_context_data.h" +#include "gin/per_isolate_data.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace edk { +namespace js { + +DrainData::DrainData(v8::Isolate* isolate, mojo::Handle handle) + : isolate_(isolate), + handle_(DataPipeConsumerHandle(handle.value())), + handle_watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) { + v8::Handle context(isolate_->GetCurrentContext()); + runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr(); + + WaitForData(); +} + +v8::Handle DrainData::GetPromise() { + CHECK(resolver_.IsEmpty()); + v8::Handle resolver( + v8::Promise::Resolver::New(isolate_)); + resolver_.Reset(isolate_, resolver); + return resolver->GetPromise(); +} + +DrainData::~DrainData() { + resolver_.Reset(); +} + +void DrainData::WaitForData() { + handle_watcher_.Watch( + handle_.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind(&DrainData::DataReady, base::Unretained(this))); +} + +void DrainData::DataReady(MojoResult result) { + if (result != MOJO_RESULT_OK) { + DeliverData(result); + return; + } + while (result == MOJO_RESULT_OK) { + result = ReadData(); + if (result == MOJO_RESULT_SHOULD_WAIT) + WaitForData(); + else if (result != MOJO_RESULT_OK) + DeliverData(result); + } +} + +MojoResult DrainData::ReadData() { + const void* buffer; + uint32_t num_bytes = 0; + MojoResult result = BeginReadDataRaw( + handle_.get(), &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE); + if (result != MOJO_RESULT_OK) + return result; + const char* p = static_cast(buffer); + data_buffers_.push_back(base::MakeUnique(p, p + num_bytes)); + return EndReadDataRaw(handle_.get(), num_bytes); +} + +void DrainData::DeliverData(MojoResult result) { + if (!runner_) { + delete this; + return; + } + + size_t total_bytes = 0; + for (unsigned i = 0; i < data_buffers_.size(); i++) + total_bytes += data_buffers_[i]->size(); + + // Create a total_bytes length ArrayBuffer return value. + gin::Runner::Scope scope(runner_.get()); + v8::Handle array_buffer = + v8::ArrayBuffer::New(isolate_, total_bytes); + gin::ArrayBuffer buffer; + ConvertFromV8(isolate_, array_buffer, &buffer); + CHECK_EQ(total_bytes, buffer.num_bytes()); + + // Copy the data_buffers into the ArrayBuffer. + char* array_buffer_ptr = static_cast(buffer.bytes()); + size_t offset = 0; + for (size_t i = 0; i < data_buffers_.size(); i++) { + size_t num_bytes = data_buffers_[i]->size(); + if (num_bytes == 0) + continue; + const char* data_buffer_ptr = &((*data_buffers_[i])[0]); + memcpy(array_buffer_ptr + offset, data_buffer_ptr, num_bytes); + offset += num_bytes; + } + + // The "settled" value of the promise always includes all of the data + // that was read before either an error occurred or the remote pipe handle + // was closed. The latter is indicated by MOJO_RESULT_FAILED_PRECONDITION. + + v8::Handle resolver( + v8::Local::New(isolate_, resolver_)); + + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate_); + dictionary.Set("result", result); + dictionary.Set("buffer", array_buffer); + v8::Handle settled_value(ConvertToV8(isolate_, dictionary)); + + if (result == MOJO_RESULT_FAILED_PRECONDITION) + resolver->Resolve(settled_value); + else + resolver->Reject(settled_value); + + delete this; +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/drain_data.h b/mojo/edk/js/drain_data.h new file mode 100644 index 0000000000000000000000000000000000000000..42da90f110b845234bd9ff76ed1aaa6ae6522b0c --- /dev/null +++ b/mojo/edk/js/drain_data.h @@ -0,0 +1,65 @@ +// Copyright 2014 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 MOJO_EDK_JS_DRAIN_DATA_H_ +#define MOJO_EDK_JS_DRAIN_DATA_H_ + +#include +#include + +#include "gin/runner.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/simple_watcher.h" +#include "v8/include/v8.h" + +namespace mojo { +namespace edk { +namespace js { + +// This class is the implementation of the Mojo JavaScript core module's +// drainData() method. It is not intended to be used directly. The caller +// allocates a DrainData on the heap and returns GetPromise() to JS. The +// implementation deletes itself after reading as much data as possible +// and rejecting or resolving the Promise. + +class DrainData { + public: + // Starts waiting for data on the specified data pipe consumer handle. + // See WaitForData(). The constructor does not block. + DrainData(v8::Isolate* isolate, mojo::Handle handle); + + // Returns a Promise that will be settled when no more data can be read. + // Should be called just once on a newly allocated DrainData object. + v8::Handle GetPromise(); + + private: + ~DrainData(); + + // Waits for data to be available. DataReady() will be notified. + void WaitForData(); + + // Use ReadData() to read whatever is availble now on handle_ and save + // it in data_buffers_. + void DataReady(MojoResult result); + MojoResult ReadData(); + + // When the remote data pipe handle is closed, or an error occurs, deliver + // all of the buffered data to the JS Promise and then delete this. + void DeliverData(MojoResult result); + + using DataBuffer = std::vector; + + v8::Isolate* isolate_; + ScopedDataPipeConsumerHandle handle_; + SimpleWatcher handle_watcher_; + base::WeakPtr runner_; + v8::UniquePersistent resolver_; + std::vector> data_buffers_; +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_DRAIN_DATA_H_ diff --git a/mojo/edk/js/handle.cc b/mojo/edk/js/handle.cc new file mode 100644 index 0000000000000000000000000000000000000000..7da8e9fa1966ad2e8fc735c29d651f40a6a521c6 --- /dev/null +++ b/mojo/edk/js/handle.cc @@ -0,0 +1,85 @@ +// Copyright 2014 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. + +#include "mojo/edk/js/handle.h" + +#include "mojo/edk/js/handle_close_observer.h" + +namespace mojo { +namespace edk { +namespace js { + +gin::WrapperInfo HandleWrapper::kWrapperInfo = { gin::kEmbedderNativeGin }; + +HandleWrapper::HandleWrapper(MojoHandle handle) + : handle_(mojo::Handle(handle)) { +} + +HandleWrapper::~HandleWrapper() { + NotifyCloseObservers(); +} + +void HandleWrapper::Close() { + NotifyCloseObservers(); + handle_.reset(); +} + +void HandleWrapper::AddCloseObserver(HandleCloseObserver* observer) { + close_observers_.AddObserver(observer); +} + +void HandleWrapper::RemoveCloseObserver(HandleCloseObserver* observer) { + close_observers_.RemoveObserver(observer); +} + +void HandleWrapper::NotifyCloseObservers() { + if (!handle_.is_valid()) + return; + + for (auto& observer : close_observers_) + observer.OnWillCloseHandle(); +} + +} // namespace js +} // namespace edk +} // namespace mojo + +namespace gin { + +v8::Handle Converter::ToV8(v8::Isolate* isolate, + const mojo::Handle& val) { + if (!val.is_valid()) + return v8::Null(isolate); + return mojo::edk::js::HandleWrapper::Create(isolate, val.value()).ToV8(); +} + +bool Converter::FromV8(v8::Isolate* isolate, + v8::Handle val, + mojo::Handle* out) { + if (val->IsNull()) { + *out = mojo::Handle(); + return true; + } + + gin::Handle handle; + if (!Converter>::FromV8( + isolate, val, &handle)) + return false; + + *out = handle->get(); + return true; +} + +v8::Handle Converter::ToV8( + v8::Isolate* isolate, mojo::MessagePipeHandle val) { + return Converter::ToV8(isolate, val); +} + +bool Converter::FromV8(v8::Isolate* isolate, + v8::Handle val, + mojo::MessagePipeHandle* out) { + return Converter::FromV8(isolate, val, out); +} + +} // namespace gin diff --git a/mojo/edk/js/handle.h b/mojo/edk/js/handle.h new file mode 100644 index 0000000000000000000000000000000000000000..60652ed8c2cc49a07d373b8d1c18bba2b67d99a5 --- /dev/null +++ b/mojo/edk/js/handle.h @@ -0,0 +1,107 @@ +// Copyright 2014 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 MOJO_EDK_JS_HANDLE_H_ +#define MOJO_EDK_JS_HANDLE_H_ + +#include + +#include "base/observer_list.h" +#include "gin/converter.h" +#include "gin/handle.h" +#include "gin/wrappable.h" +#include "mojo/edk/js/js_export.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace edk { +namespace js { + +class HandleCloseObserver; + +// Wrapper for mojo Handles exposed to JavaScript. This ensures the Handle +// is Closed when its JS object is garbage collected. +class MOJO_JS_EXPORT HandleWrapper : public gin::Wrappable { + public: + static gin::WrapperInfo kWrapperInfo; + + static gin::Handle Create(v8::Isolate* isolate, + MojoHandle handle) { + return gin::CreateHandle(isolate, new HandleWrapper(handle)); + } + + mojo::Handle get() const { return handle_.get(); } + mojo::Handle release() { return handle_.release(); } + void Close(); + + void AddCloseObserver(HandleCloseObserver* observer); + void RemoveCloseObserver(HandleCloseObserver* observer); + + protected: + HandleWrapper(MojoHandle handle); + ~HandleWrapper() override; + void NotifyCloseObservers(); + + mojo::ScopedHandle handle_; + base::ObserverList close_observers_; +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +namespace gin { + +// Note: It's important to use this converter rather than the one for +// MojoHandle, since that will do a simple int32_t conversion. It's unfortunate +// there's no way to prevent against accidental use. +// TODO(mpcomplete): define converters for all Handle subtypes. +template <> +struct MOJO_JS_EXPORT Converter { + static v8::Handle ToV8(v8::Isolate* isolate, + const mojo::Handle& val); + static bool FromV8(v8::Isolate* isolate, v8::Handle val, + mojo::Handle* out); +}; + +template <> +struct MOJO_JS_EXPORT Converter { + static v8::Handle ToV8(v8::Isolate* isolate, + mojo::MessagePipeHandle val); + static bool FromV8(v8::Isolate* isolate, + v8::Handle val, + mojo::MessagePipeHandle* out); +}; + +// We need to specialize the normal gin::Handle converter in order to handle +// converting |null| to a wrapper for an empty mojo::Handle. +template <> +struct MOJO_JS_EXPORT Converter> { + static v8::Handle ToV8( + v8::Isolate* isolate, + const gin::Handle& val) { + return val.ToV8(); + } + + static bool FromV8(v8::Isolate* isolate, + v8::Handle val, + gin::Handle* out) { + if (val->IsNull()) { + *out = mojo::edk::js::HandleWrapper::Create(isolate, MOJO_HANDLE_INVALID); + return true; + } + + mojo::edk::js::HandleWrapper* object = NULL; + if (!Converter::FromV8(isolate, val, + &object)) { + return false; + } + *out = gin::Handle(val, object); + return true; + } +}; + +} // namespace gin + +#endif // MOJO_EDK_JS_HANDLE_H_ diff --git a/mojo/edk/js/handle_close_observer.h b/mojo/edk/js/handle_close_observer.h new file mode 100644 index 0000000000000000000000000000000000000000..c7b935ec6ecb3d4f99e9a0e5dd6627e5ffadf22b --- /dev/null +++ b/mojo/edk/js/handle_close_observer.h @@ -0,0 +1,24 @@ +// Copyright 2014 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 MOJO_EDK_JS_HANDLE_CLOSE_OBSERVER_H_ +#define MOJO_EDK_JS_HANDLE_CLOSE_OBSERVER_H_ + +namespace mojo { +namespace edk { +namespace js { + +class HandleCloseObserver { + public: + virtual void OnWillCloseHandle() = 0; + + protected: + virtual ~HandleCloseObserver() {} +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_HANDLE_CLOSE_OBSERVER_H_ diff --git a/mojo/edk/js/handle_unittest.cc b/mojo/edk/js/handle_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..dd2562fa6378d73656d95164d422e47246d87511 --- /dev/null +++ b/mojo/edk/js/handle_unittest.cc @@ -0,0 +1,92 @@ +// Copyright 2014 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. + +#include "base/macros.h" +#include "mojo/edk/js/handle.h" +#include "mojo/edk/js/handle_close_observer.h" +#include "mojo/public/cpp/system/core.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace js { + +class HandleWrapperTest : public testing::Test, + public HandleCloseObserver { + public: + HandleWrapperTest() : closes_observed_(0) {} + + void OnWillCloseHandle() override { closes_observed_++; } + + protected: + int closes_observed_; + + private: + DISALLOW_COPY_AND_ASSIGN(HandleWrapperTest); +}; + +class TestHandleWrapper : public HandleWrapper { + public: + explicit TestHandleWrapper(MojoHandle handle) : HandleWrapper(handle) {} + + private: + DISALLOW_COPY_AND_ASSIGN(TestHandleWrapper); +}; + +// Test that calling Close() on a HandleWrapper for an invalid handle does not +// notify observers. +TEST_F(HandleWrapperTest, CloseWithInvalidHandle) { + { + TestHandleWrapper wrapper(MOJO_HANDLE_INVALID); + wrapper.AddCloseObserver(this); + ASSERT_EQ(0, closes_observed_); + wrapper.Close(); + EXPECT_EQ(0, closes_observed_); + } + EXPECT_EQ(0, closes_observed_); +} + +// Test that destroying a HandleWrapper for an invalid handle does not notify +// observers. +TEST_F(HandleWrapperTest, DestroyWithInvalidHandle) { + { + TestHandleWrapper wrapper(MOJO_HANDLE_INVALID); + wrapper.AddCloseObserver(this); + ASSERT_EQ(0, closes_observed_); + } + EXPECT_EQ(0, closes_observed_); +} + +// Test that calling Close on a HandleWrapper for a valid handle notifies +// observers once. +TEST_F(HandleWrapperTest, CloseWithValidHandle) { + { + mojo::MessagePipe pipe; + TestHandleWrapper wrapper(pipe.handle0.release().value()); + wrapper.AddCloseObserver(this); + ASSERT_EQ(0, closes_observed_); + wrapper.Close(); + EXPECT_EQ(1, closes_observed_); + // Check that calling close again doesn't notify observers. + wrapper.Close(); + EXPECT_EQ(1, closes_observed_); + } + // Check that destroying a closed HandleWrapper doesn't notify observers. + EXPECT_EQ(1, closes_observed_); +} + +// Test that destroying a HandleWrapper for a valid handle notifies observers. +TEST_F(HandleWrapperTest, DestroyWithValidHandle) { + { + mojo::MessagePipe pipe; + TestHandleWrapper wrapper(pipe.handle0.release().value()); + wrapper.AddCloseObserver(this); + ASSERT_EQ(0, closes_observed_); + } + EXPECT_EQ(1, closes_observed_); +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/js_export.h b/mojo/edk/js/js_export.h new file mode 100644 index 0000000000000000000000000000000000000000..179113ce4fe04cbf3239b36e83a2bfe1837e39ae --- /dev/null +++ b/mojo/edk/js/js_export.h @@ -0,0 +1,32 @@ +// Copyright 2016 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 MOJO_EDK_JS_JS_EXPORT_H_ +#define MOJO_EDK_JS_JS_EXPORT_H_ + +// Defines MOJO_JS_EXPORT so that functionality implemented by //mojo/edk/js can +// be exported to consumers. + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_JS_IMPLEMENTATION) +#define MOJO_JS_EXPORT __declspec(dllexport) +#else +#define MOJO_JS_EXPORT __declspec(dllimport) +#endif // defined(MOJO_JS_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(MOJO_JS_IMPLEMENTATION) +#define MOJO_JS_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_JS_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define MOJO_JS_EXPORT +#endif + +#endif // MOJO_EDK_JS_JS_EXPORT_H_ diff --git a/mojo/edk/js/mojo_runner_delegate.cc b/mojo/edk/js/mojo_runner_delegate.cc new file mode 100644 index 0000000000000000000000000000000000000000..dda0b2c315ae50dfb49e86a8815697d6ea856cdd --- /dev/null +++ b/mojo/edk/js/mojo_runner_delegate.cc @@ -0,0 +1,80 @@ +// Copyright 2013 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. + +#include "mojo/edk/js/mojo_runner_delegate.h" + +#include "base/bind.h" +#include "base/path_service.h" +#include "gin/converter.h" +#include "gin/modules/console.h" +#include "gin/modules/module_registry.h" +#include "gin/modules/timer.h" +#include "gin/try_catch.h" +#include "mojo/edk/js/core.h" +#include "mojo/edk/js/handle.h" +#include "mojo/edk/js/support.h" +#include "mojo/edk/js/threading.h" + +namespace mojo { +namespace edk { +namespace js { + +namespace { + +// TODO(abarth): Rather than loading these modules from the file system, we +// should load them from the network via Mojo IPC. +std::vector GetModuleSearchPaths() { + std::vector search_paths(2); + PathService::Get(base::DIR_SOURCE_ROOT, &search_paths[0]); + PathService::Get(base::DIR_EXE, &search_paths[1]); + search_paths[1] = search_paths[1].AppendASCII("gen"); + return search_paths; +} + +void StartCallback(base::WeakPtr runner, + MojoHandle pipe, + v8::Handle module) { + v8::Isolate* isolate = runner->GetContextHolder()->isolate(); + v8::Handle start; + CHECK(gin::ConvertFromV8(isolate, module, &start)); + + v8::Handle args[] = { + gin::ConvertToV8(isolate, Handle(pipe)) }; + runner->Call(start, runner->global(), 1, args); +} + +} // namespace + +MojoRunnerDelegate::MojoRunnerDelegate() + : ModuleRunnerDelegate(GetModuleSearchPaths()) { + AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule); + AddBuiltinModule(gin::TimerModule::kName, gin::TimerModule::GetModule); + AddBuiltinModule(Core::kModuleName, Core::GetModule); + AddBuiltinModule(Support::kModuleName, Support::GetModule); + AddBuiltinModule(Threading::kModuleName, Threading::GetModule); +} + +MojoRunnerDelegate::~MojoRunnerDelegate() { +} + +void MojoRunnerDelegate::Start(gin::Runner* runner, + MojoHandle pipe, + const std::string& module) { + gin::Runner::Scope scope(runner); + gin::ModuleRegistry* registry = + gin::ModuleRegistry::From(runner->GetContextHolder()->context()); + registry->LoadModule(runner->GetContextHolder()->isolate(), module, + base::Bind(StartCallback, runner->GetWeakPtr(), pipe)); + AttemptToLoadMoreModules(runner); +} + +void MojoRunnerDelegate::UnhandledException(gin::ShellRunner* runner, + gin::TryCatch& try_catch) { + gin::ModuleRunnerDelegate::UnhandledException(runner, try_catch); + LOG(ERROR) << try_catch.GetStackTrace(); +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/mojo_runner_delegate.h b/mojo/edk/js/mojo_runner_delegate.h new file mode 100644 index 0000000000000000000000000000000000000000..9ab325c8dfb484e4895cddd9f226c2e4db109dc8 --- /dev/null +++ b/mojo/edk/js/mojo_runner_delegate.h @@ -0,0 +1,36 @@ +// Copyright 2013 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 MOJO_EDK_JS_MOJO_RUNNER_DELEGATE_H_ +#define MOJO_EDK_JS_MOJO_RUNNER_DELEGATE_H_ + +#include "base/macros.h" +#include "gin/modules/module_runner_delegate.h" +#include "mojo/edk/js/js_export.h" +#include "mojo/public/c/system/core.h" + +namespace mojo { +namespace edk { +namespace js { + +class MOJO_JS_EXPORT MojoRunnerDelegate : public gin::ModuleRunnerDelegate { + public: + MojoRunnerDelegate(); + ~MojoRunnerDelegate() override; + + void Start(gin::Runner* runner, MojoHandle pipe, const std::string& module); + + private: + // From ModuleRunnerDelegate: + void UnhandledException(gin::ShellRunner* runner, + gin::TryCatch& try_catch) override; + + DISALLOW_COPY_AND_ASSIGN(MojoRunnerDelegate); +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_MOJO_RUNNER_DELEGATE_H_ diff --git a/mojo/edk/js/support.cc b/mojo/edk/js/support.cc new file mode 100644 index 0000000000000000000000000000000000000000..404cb9b786651c90fbfc527f6404547e81955ad4 --- /dev/null +++ b/mojo/edk/js/support.cc @@ -0,0 +1,77 @@ +// Copyright 2014 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. + +#include "mojo/edk/js/support.h" + +#include "base/bind.h" +#include "gin/arguments.h" +#include "gin/converter.h" +#include "gin/function_template.h" +#include "gin/object_template_builder.h" +#include "gin/per_isolate_data.h" +#include "gin/public/wrapper_info.h" +#include "gin/wrappable.h" +#include "mojo/edk/js/handle.h" +#include "mojo/edk/js/waiting_callback.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace edk { +namespace js { + +namespace { + +WaitingCallback* AsyncWait(const gin::Arguments& args, + gin::Handle handle, + MojoHandleSignals signals, + v8::Handle callback) { + return WaitingCallback::Create( + args.isolate(), callback, handle, signals, true /* one_shot */).get(); +} + +void CancelWait(WaitingCallback* waiting_callback) { + waiting_callback->Cancel(); +} + +WaitingCallback* Watch(const gin::Arguments& args, + gin::Handle handle, + MojoHandleSignals signals, + v8::Handle callback) { + return WaitingCallback::Create( + args.isolate(), callback, handle, signals, false /* one_shot */).get(); +} + +void CancelWatch(WaitingCallback* waiting_callback) { + waiting_callback->Cancel(); +} + +gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin }; + +} // namespace + +const char Support::kModuleName[] = "mojo/public/js/support"; + +v8::Local Support::GetModule(v8::Isolate* isolate) { + gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); + v8::Local templ = data->GetObjectTemplate( + &g_wrapper_info); + + if (templ.IsEmpty()) { + templ = gin::ObjectTemplateBuilder(isolate) + // TODO(rockot): Remove asyncWait and cancelWait. + .SetMethod("asyncWait", AsyncWait) + .SetMethod("cancelWait", CancelWait) + .SetMethod("watch", Watch) + .SetMethod("cancelWatch", CancelWatch) + .Build(); + + data->SetObjectTemplate(&g_wrapper_info, templ); + } + + return templ->NewInstance(); +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/support.h b/mojo/edk/js/support.h new file mode 100644 index 0000000000000000000000000000000000000000..551f5ac0a82fb0ba281a5bf044ef502193572583 --- /dev/null +++ b/mojo/edk/js/support.h @@ -0,0 +1,25 @@ +// Copyright 2014 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 MOJO_EDK_JS_SUPPORT_H_ +#define MOJO_EDK_JS_SUPPORT_H_ + +#include "mojo/edk/js/js_export.h" +#include "v8/include/v8.h" + +namespace mojo { +namespace edk { +namespace js { + +class MOJO_JS_EXPORT Support { + public: + static const char kModuleName[]; + static v8::Local GetModule(v8::Isolate* isolate); +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_SUPPORT_H_ diff --git a/mojo/edk/js/tests/BUILD.gn b/mojo/edk/js/tests/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..f56c4b95f9a110f48d01140d1312d0d3137cdef8 --- /dev/null +++ b/mojo/edk/js/tests/BUILD.gn @@ -0,0 +1,68 @@ +# Copyright 2014 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("//mojo/public/tools/bindings/mojom.gni") +import("//testing/test.gni") + +# TODO(hansmuller): The organization of tests in this directory is weird: +# * Really, js_unittests tests public stuff, so that should live in public +# and be reworked as some sort of apptest. +# * Both js_unittests and js_integration_tests should auto-generate their +# tests somehow. The .cc files are just test runner stubs, including +# explicit lists of .js files. + +group("tests") { + testonly = true + deps = [ + ":mojo_js_integration_tests", + ":mojo_js_unittests", + ] +} + +test("mojo_js_integration_tests") { + deps = [ + ":js_to_cpp_bindings", + "//gin:gin_test", + "//mojo/common", + "//mojo/edk/js", + "//mojo/edk/test:run_all_unittests", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/system", + "//mojo/public/js:bindings", + ] + + sources = [ + "js_to_cpp_tests.cc", + ] + + data = [ + "js_to_cpp_tests.js", + ] + + configs += [ "//v8:external_startup_data" ] +} + +mojom("js_to_cpp_bindings") { + sources = [ + "js_to_cpp.mojom", + ] +} + +test("mojo_js_unittests") { + deps = [ + "//base", + "//gin:gin_test", + "//mojo/edk/js", + "//mojo/edk/test:run_all_unittests", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/system", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//mojo/public/js:tests", + ] + + sources = [ + "//mojo/edk/js/handle_unittest.cc", + "run_js_unittests.cc", + ] +} diff --git a/mojo/edk/js/tests/js_to_cpp.mojom b/mojo/edk/js/tests/js_to_cpp.mojom new file mode 100644 index 0000000000000000000000000000000000000000..688b22b3dea8321a9d402535f5983bebae8b2cb5 --- /dev/null +++ b/mojo/edk/js/tests/js_to_cpp.mojom @@ -0,0 +1,54 @@ +module js_to_cpp; + +// This struct encompasses all of the basic types, so that they +// may be sent from C++ to JS and back for validation. +struct EchoArgs { + int64 si64; + int32 si32; + int16 si16; + int8 si8; + uint64 ui64; + uint32 ui32; + uint16 ui16; + uint8 ui8; + float float_val; + float float_inf; + float float_nan; + double double_val; + double double_inf; + double double_nan; + string? name; + array? string_array; + handle? message_handle; + handle? data_handle; +}; + +struct EchoArgsList { + EchoArgsList? next; + EchoArgs? item; +}; + +// Note: For messages which control test flow, pick numbers that are unlikely +// to be hit as a result of our deliberate corruption of response messages. +interface CppSide { + // Sent for all tests to notify that the JS side is now ready. + StartTest@88888888(); + + // Indicates end for echo, bit-flip, and back-pointer tests. + TestFinished@99999999(); + + // Responses from specific tests. + PingResponse(); + EchoResponse(EchoArgsList list); + BitFlipResponse(EchoArgsList arg); + BackPointerResponse(EchoArgsList arg); +}; + +interface JsSide { + SetCppSide(CppSide cpp); + + Ping(); + Echo(int32 numIterations, EchoArgs arg); + BitFlip(EchoArgs arg); + BackPointer(EchoArgs arg); +}; diff --git a/mojo/edk/js/tests/js_to_cpp_tests.cc b/mojo/edk/js/tests/js_to_cpp_tests.cc new file mode 100644 index 0000000000000000000000000000000000000000..b6b74e31fbfdc692fc2b8ec00faef1d82c215471 --- /dev/null +++ b/mojo/edk/js/tests/js_to_cpp_tests.cc @@ -0,0 +1,455 @@ +// Copyright 2014 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. + +#include +#include + +#include +#include + +#include "base/at_exit.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "gin/array_buffer.h" +#include "gin/public/isolate_holder.h" +#include "gin/v8_initializer.h" +#include "mojo/common/data_pipe_utils.h" +#include "mojo/edk/js/mojo_runner_delegate.h" +#include "mojo/edk/js/tests/js_to_cpp.mojom.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace js { + +// Global value updated by some checks to prevent compilers from optimizing +// reads out of existence. +uint32_t g_waste_accumulator = 0; + +namespace { + +// Negative numbers with different values in each byte, the last of +// which can survive promotion to double and back. +const int8_t kExpectedInt8Value = -65; +const int16_t kExpectedInt16Value = -16961; +const int32_t kExpectedInt32Value = -1145258561; +const int64_t kExpectedInt64Value = -77263311946305LL; + +// Positive numbers with different values in each byte, the last of +// which can survive promotion to double and back. +const uint8_t kExpectedUInt8Value = 65; +const uint16_t kExpectedUInt16Value = 16961; +const uint32_t kExpectedUInt32Value = 1145258561; +const uint64_t kExpectedUInt64Value = 77263311946305LL; + +// Double/float values, including special case constants. +const double kExpectedDoubleVal = 3.14159265358979323846; +const double kExpectedDoubleInf = std::numeric_limits::infinity(); +const double kExpectedDoubleNan = std::numeric_limits::quiet_NaN(); +const float kExpectedFloatVal = static_cast(kExpectedDoubleVal); +const float kExpectedFloatInf = std::numeric_limits::infinity(); +const float kExpectedFloatNan = std::numeric_limits::quiet_NaN(); + +// NaN has the property that it is not equal to itself. +#define EXPECT_NAN(x) EXPECT_NE(x, x) + +void CheckDataPipe(ScopedDataPipeConsumerHandle data_pipe_handle) { + std::string buffer; + bool result = common::BlockingCopyToString(std::move(data_pipe_handle), + &buffer); + EXPECT_TRUE(result); + EXPECT_EQ(64u, buffer.size()); + for (int i = 0; i < 64; ++i) { + EXPECT_EQ(i, buffer[i]); + } +} + +void CheckMessagePipe(MessagePipeHandle message_pipe_handle) { + unsigned char buffer[100]; + uint32_t buffer_size = static_cast(sizeof(buffer)); + MojoResult result = Wait(message_pipe_handle, MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_RESULT_OK, result); + result = ReadMessageRaw( + message_pipe_handle, buffer, &buffer_size, 0, 0, 0); + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(64u, buffer_size); + for (int i = 0; i < 64; ++i) { + EXPECT_EQ(255 - i, buffer[i]); + } +} + +js_to_cpp::EchoArgsPtr BuildSampleEchoArgs() { + js_to_cpp::EchoArgsPtr args(js_to_cpp::EchoArgs::New()); + args->si64 = kExpectedInt64Value; + args->si32 = kExpectedInt32Value; + args->si16 = kExpectedInt16Value; + args->si8 = kExpectedInt8Value; + args->ui64 = kExpectedUInt64Value; + args->ui32 = kExpectedUInt32Value; + args->ui16 = kExpectedUInt16Value; + args->ui8 = kExpectedUInt8Value; + args->float_val = kExpectedFloatVal; + args->float_inf = kExpectedFloatInf; + args->float_nan = kExpectedFloatNan; + args->double_val = kExpectedDoubleVal; + args->double_inf = kExpectedDoubleInf; + args->double_nan = kExpectedDoubleNan; + args->name.emplace("coming"); + args->string_array.emplace(3); + (*args->string_array)[0] = "one"; + (*args->string_array)[1] = "two"; + (*args->string_array)[2] = "three"; + return args; +} + +void CheckSampleEchoArgs(js_to_cpp::EchoArgsPtr arg) { + EXPECT_EQ(kExpectedInt64Value, arg->si64); + EXPECT_EQ(kExpectedInt32Value, arg->si32); + EXPECT_EQ(kExpectedInt16Value, arg->si16); + EXPECT_EQ(kExpectedInt8Value, arg->si8); + EXPECT_EQ(kExpectedUInt64Value, arg->ui64); + EXPECT_EQ(kExpectedUInt32Value, arg->ui32); + EXPECT_EQ(kExpectedUInt16Value, arg->ui16); + EXPECT_EQ(kExpectedUInt8Value, arg->ui8); + EXPECT_EQ(kExpectedFloatVal, arg->float_val); + EXPECT_EQ(kExpectedFloatInf, arg->float_inf); + EXPECT_NAN(arg->float_nan); + EXPECT_EQ(kExpectedDoubleVal, arg->double_val); + EXPECT_EQ(kExpectedDoubleInf, arg->double_inf); + EXPECT_NAN(arg->double_nan); + EXPECT_EQ(std::string("coming"), *arg->name); + EXPECT_EQ(std::string("one"), (*arg->string_array)[0]); + EXPECT_EQ(std::string("two"), (*arg->string_array)[1]); + EXPECT_EQ(std::string("three"), (*arg->string_array)[2]); + CheckDataPipe(std::move(arg->data_handle)); + CheckMessagePipe(arg->message_handle.get()); +} + +void CheckSampleEchoArgsList(const js_to_cpp::EchoArgsListPtr& list) { + if (list.is_null()) + return; + CheckSampleEchoArgs(std::move(list->item)); + CheckSampleEchoArgsList(list->next); +} + +// More forgiving checks are needed in the face of potentially corrupt +// messages. The values don't matter so long as all accesses are within +// bounds. +void CheckCorruptedString(const std::string& arg) { + for (size_t i = 0; i < arg.size(); ++i) + g_waste_accumulator += arg[i]; +} + +void CheckCorruptedString(const base::Optional& arg) { + if (!arg) + return; + CheckCorruptedString(*arg); +} + +void CheckCorruptedStringArray( + const base::Optional>& string_array) { + if (!string_array) + return; + for (size_t i = 0; i < string_array->size(); ++i) + CheckCorruptedString((*string_array)[i]); +} + +void CheckCorruptedDataPipe(MojoHandle data_pipe_handle) { + unsigned char buffer[100]; + uint32_t buffer_size = static_cast(sizeof(buffer)); + MojoResult result = MojoReadData( + data_pipe_handle, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE); + if (result != MOJO_RESULT_OK) + return; + for (uint32_t i = 0; i < buffer_size; ++i) + g_waste_accumulator += buffer[i]; +} + +void CheckCorruptedMessagePipe(MojoHandle message_pipe_handle) { + unsigned char buffer[100]; + uint32_t buffer_size = static_cast(sizeof(buffer)); + MojoResult result = MojoReadMessage( + message_pipe_handle, buffer, &buffer_size, 0, 0, 0); + if (result != MOJO_RESULT_OK) + return; + for (uint32_t i = 0; i < buffer_size; ++i) + g_waste_accumulator += buffer[i]; +} + +void CheckCorruptedEchoArgs(const js_to_cpp::EchoArgsPtr& arg) { + if (arg.is_null()) + return; + CheckCorruptedString(arg->name); + CheckCorruptedStringArray(arg->string_array); + if (arg->data_handle.is_valid()) + CheckCorruptedDataPipe(arg->data_handle.get().value()); + if (arg->message_handle.is_valid()) + CheckCorruptedMessagePipe(arg->message_handle.get().value()); +} + +void CheckCorruptedEchoArgsList(const js_to_cpp::EchoArgsListPtr& list) { + if (list.is_null()) + return; + CheckCorruptedEchoArgs(list->item); + CheckCorruptedEchoArgsList(list->next); +} + +// Base Provider implementation class. It's expected that tests subclass and +// override the appropriate Provider functions. When test is done quit the +// run_loop(). +class CppSideConnection : public js_to_cpp::CppSide { + public: + CppSideConnection() + : run_loop_(nullptr), + js_side_(nullptr), + mishandled_messages_(0), + binding_(this) {} + ~CppSideConnection() override {} + + void set_run_loop(base::RunLoop* run_loop) { run_loop_ = run_loop; } + base::RunLoop* run_loop() { return run_loop_; } + + void set_js_side(js_to_cpp::JsSide* js_side) { js_side_ = js_side; } + js_to_cpp::JsSide* js_side() { return js_side_; } + + void Bind(InterfaceRequest request) { + binding_.Bind(std::move(request)); + // Keep the pipe open even after validation errors. + binding_.EnableTestingMode(); + } + + // js_to_cpp::CppSide: + void StartTest() override { NOTREACHED(); } + + void TestFinished() override { NOTREACHED(); } + + void PingResponse() override { mishandled_messages_ += 1; } + + void EchoResponse(js_to_cpp::EchoArgsListPtr list) override { + mishandled_messages_ += 1; + } + + void BitFlipResponse(js_to_cpp::EchoArgsListPtr list) override { + mishandled_messages_ += 1; + } + + void BackPointerResponse(js_to_cpp::EchoArgsListPtr list) override { + mishandled_messages_ += 1; + } + + protected: + base::RunLoop* run_loop_; + js_to_cpp::JsSide* js_side_; + int mishandled_messages_; + mojo::Binding binding_; + + private: + DISALLOW_COPY_AND_ASSIGN(CppSideConnection); +}; + +// Trivial test to verify a message sent from JS is received. +class PingCppSideConnection : public CppSideConnection { + public: + PingCppSideConnection() : got_message_(false) {} + ~PingCppSideConnection() override {} + + // js_to_cpp::CppSide: + void StartTest() override { js_side_->Ping(); } + + void PingResponse() override { + got_message_ = true; + run_loop()->Quit(); + } + + bool DidSucceed() { + return got_message_ && !mishandled_messages_; + } + + private: + bool got_message_; + DISALLOW_COPY_AND_ASSIGN(PingCppSideConnection); +}; + +// Test that parameters are passed with correct values. +class EchoCppSideConnection : public CppSideConnection { + public: + EchoCppSideConnection() : + message_count_(0), + termination_seen_(false) { + } + ~EchoCppSideConnection() override {} + + // js_to_cpp::CppSide: + void StartTest() override { + js_side_->Echo(kExpectedMessageCount, BuildSampleEchoArgs()); + } + + void EchoResponse(js_to_cpp::EchoArgsListPtr list) override { + const js_to_cpp::EchoArgsPtr& special_arg = list->item; + message_count_ += 1; + EXPECT_EQ(-1, special_arg->si64); + EXPECT_EQ(-1, special_arg->si32); + EXPECT_EQ(-1, special_arg->si16); + EXPECT_EQ(-1, special_arg->si8); + EXPECT_EQ(std::string("going"), *special_arg->name); + CheckSampleEchoArgsList(list->next); + } + + void TestFinished() override { + termination_seen_ = true; + run_loop()->Quit(); + } + + bool DidSucceed() { + return termination_seen_ && + !mishandled_messages_ && + message_count_ == kExpectedMessageCount; + } + + private: + static const int kExpectedMessageCount = 10; + int message_count_; + bool termination_seen_; + DISALLOW_COPY_AND_ASSIGN(EchoCppSideConnection); +}; + +// Test that corrupted messages don't wreak havoc. +class BitFlipCppSideConnection : public CppSideConnection { + public: + BitFlipCppSideConnection() : termination_seen_(false) {} + ~BitFlipCppSideConnection() override {} + + // js_to_cpp::CppSide: + void StartTest() override { js_side_->BitFlip(BuildSampleEchoArgs()); } + + void BitFlipResponse(js_to_cpp::EchoArgsListPtr list) override { + CheckCorruptedEchoArgsList(list); + } + + void TestFinished() override { + termination_seen_ = true; + run_loop()->Quit(); + } + + bool DidSucceed() { + return termination_seen_; + } + + private: + bool termination_seen_; + DISALLOW_COPY_AND_ASSIGN(BitFlipCppSideConnection); +}; + +// Test that severely random messages don't wreak havoc. +class BackPointerCppSideConnection : public CppSideConnection { + public: + BackPointerCppSideConnection() : termination_seen_(false) {} + ~BackPointerCppSideConnection() override {} + + // js_to_cpp::CppSide: + void StartTest() override { js_side_->BackPointer(BuildSampleEchoArgs()); } + + void BackPointerResponse(js_to_cpp::EchoArgsListPtr list) override { + CheckCorruptedEchoArgsList(list); + } + + void TestFinished() override { + termination_seen_ = true; + run_loop()->Quit(); + } + + bool DidSucceed() { + return termination_seen_; + } + + private: + bool termination_seen_; + DISALLOW_COPY_AND_ASSIGN(BackPointerCppSideConnection); +}; + +} // namespace + +class JsToCppTest : public testing::Test { + public: + JsToCppTest() {} + + void RunTest(const std::string& test, CppSideConnection* cpp_side) { + cpp_side->set_run_loop(&run_loop_); + + js_to_cpp::JsSidePtr js_side; + auto js_side_proxy = MakeRequest(&js_side); + + cpp_side->set_js_side(js_side.get()); + js_to_cpp::CppSidePtr cpp_side_ptr; + cpp_side->Bind(MakeRequest(&cpp_side_ptr)); + + js_side->SetCppSide(std::move(cpp_side_ptr)); + +#ifdef V8_USE_EXTERNAL_STARTUP_DATA + gin::V8Initializer::LoadV8Snapshot(); + gin::V8Initializer::LoadV8Natives(); +#endif + + gin::IsolateHolder::Initialize(gin::IsolateHolder::kStrictMode, + gin::IsolateHolder::kStableV8Extras, + gin::ArrayBufferAllocator::SharedInstance()); + gin::IsolateHolder instance(base::ThreadTaskRunnerHandle::Get()); + MojoRunnerDelegate delegate; + gin::ShellRunner runner(&delegate, instance.isolate()); + delegate.Start(&runner, js_side_proxy.PassMessagePipe().release().value(), + test); + + run_loop_.Run(); + } + + private: + base::ShadowingAtExitManager at_exit_; + base::MessageLoop loop; + base::RunLoop run_loop_; + + DISALLOW_COPY_AND_ASSIGN(JsToCppTest); +}; + +TEST_F(JsToCppTest, Ping) { + PingCppSideConnection cpp_side_connection; + RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection); + EXPECT_TRUE(cpp_side_connection.DidSucceed()); +} + +TEST_F(JsToCppTest, Echo) { + EchoCppSideConnection cpp_side_connection; + RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection); + EXPECT_TRUE(cpp_side_connection.DidSucceed()); +} + +TEST_F(JsToCppTest, BitFlip) { + // These tests generate a lot of expected validation errors. Suppress logging. + mojo::internal::ScopedSuppressValidationErrorLoggingForTests log_suppression; + + BitFlipCppSideConnection cpp_side_connection; + RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection); + EXPECT_TRUE(cpp_side_connection.DidSucceed()); +} + +TEST_F(JsToCppTest, BackPointer) { + // These tests generate a lot of expected validation errors. Suppress logging. + mojo::internal::ScopedSuppressValidationErrorLoggingForTests log_suppression; + + BackPointerCppSideConnection cpp_side_connection; + RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection); + EXPECT_TRUE(cpp_side_connection.DidSucceed()); +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/tests/js_to_cpp_tests.js b/mojo/edk/js/tests/js_to_cpp_tests.js new file mode 100644 index 0000000000000000000000000000000000000000..6b69fcacc8bea86b8d5be6e22dba055d5e6ee2fc --- /dev/null +++ b/mojo/edk/js/tests/js_to_cpp_tests.js @@ -0,0 +1,223 @@ +// Copyright 2014 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. + +define('mojo/edk/js/tests/js_to_cpp_tests', [ + 'console', + 'mojo/edk/js/tests/js_to_cpp.mojom', + 'mojo/public/js/bindings', + 'mojo/public/js/connector', + 'mojo/public/js/core', +], function (console, jsToCpp, bindings, connector, core) { + var retainedJsSide; + var retainedJsSideStub; + var sampleData; + var sampleMessage; + var BAD_VALUE = 13; + var DATA_PIPE_PARAMS = { + flags: core.CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, + elementNumBytes: 1, + capacityNumBytes: 64 + }; + + function JsSideConnection() { + this.binding = new bindings.Binding(jsToCpp.JsSide, this); + } + + JsSideConnection.prototype.setCppSide = function(cppSide) { + this.cppSide_ = cppSide; + this.cppSide_.startTest(); + }; + + JsSideConnection.prototype.ping = function (arg) { + this.cppSide_.pingResponse(); + }; + + JsSideConnection.prototype.echo = function (numIterations, arg) { + var dataPipe1; + var dataPipe2; + var i; + var messagePipe1; + var messagePipe2; + var specialArg; + + // Ensure expected negative values are negative. + if (arg.si64 > 0) + arg.si64 = BAD_VALUE; + + if (arg.si32 > 0) + arg.si32 = BAD_VALUE; + + if (arg.si16 > 0) + arg.si16 = BAD_VALUE; + + if (arg.si8 > 0) + arg.si8 = BAD_VALUE; + + for (i = 0; i < numIterations; ++i) { + dataPipe1 = core.createDataPipe(DATA_PIPE_PARAMS); + dataPipe2 = core.createDataPipe(DATA_PIPE_PARAMS); + messagePipe1 = core.createMessagePipe(); + messagePipe2 = core.createMessagePipe(); + + arg.data_handle = dataPipe1.consumerHandle; + arg.message_handle = messagePipe1.handle1; + + specialArg = new jsToCpp.EchoArgs(); + specialArg.si64 = -1; + specialArg.si32 = -1; + specialArg.si16 = -1; + specialArg.si8 = -1; + specialArg.name = 'going'; + specialArg.data_handle = dataPipe2.consumerHandle; + specialArg.message_handle = messagePipe2.handle1; + + writeDataPipe(dataPipe1, sampleData); + writeDataPipe(dataPipe2, sampleData); + writeMessagePipe(messagePipe1, sampleMessage); + writeMessagePipe(messagePipe2, sampleMessage); + + this.cppSide_.echoResponse(createEchoArgsList(specialArg, arg)); + + core.close(dataPipe1.producerHandle); + core.close(dataPipe2.producerHandle); + core.close(messagePipe1.handle0); + core.close(messagePipe2.handle0); + } + this.cppSide_.testFinished(); + }; + + JsSideConnection.prototype.bitFlip = function (arg) { + var iteration = 0; + var dataPipe; + var messagePipe; + var proto = connector.Connector.prototype; + var stopSignalled = false; + + proto.realAccept = proto.accept; + proto.accept = function (message) { + var offset = iteration / 8; + var mask; + var value; + if (offset < message.buffer.arrayBuffer.byteLength) { + mask = 1 << (iteration % 8); + value = message.buffer.getUint8(offset) ^ mask; + message.buffer.setUint8(offset, value); + return this.realAccept(message); + } + stopSignalled = true; + return false; + }; + + while (!stopSignalled) { + dataPipe = core.createDataPipe(DATA_PIPE_PARAMS); + messagePipe = core.createMessagePipe(); + writeDataPipe(dataPipe, sampleData); + writeMessagePipe(messagePipe, sampleMessage); + arg.data_handle = dataPipe.consumerHandle; + arg.message_handle = messagePipe.handle1; + + this.cppSide_.bitFlipResponse(createEchoArgsList(arg)); + + core.close(dataPipe.producerHandle); + core.close(messagePipe.handle0); + iteration += 1; + } + + proto.accept = proto.realAccept; + proto.realAccept = null; + this.cppSide_.testFinished(); + }; + + JsSideConnection.prototype.backPointer = function (arg) { + var iteration = 0; + var dataPipe; + var messagePipe; + var proto = connector.Connector.prototype; + var stopSignalled = false; + + proto.realAccept = proto.accept; + proto.accept = function (message) { + var delta = 8 * (1 + iteration % 32); + var offset = 8 * ((iteration / 32) | 0); + if (offset < message.buffer.arrayBuffer.byteLength - 4) { + message.buffer.dataView.setUint32(offset, 0x100000000 - delta, true); + message.buffer.dataView.setUint32(offset + 4, 0xffffffff, true); + return this.realAccept(message); + } + stopSignalled = true; + return false; + }; + + while (!stopSignalled) { + dataPipe = core.createDataPipe(DATA_PIPE_PARAMS); + messagePipe = core.createMessagePipe(); + writeDataPipe(dataPipe, sampleData); + writeMessagePipe(messagePipe, sampleMessage); + arg.data_handle = dataPipe.consumerHandle; + arg.message_handle = messagePipe.handle1; + + this.cppSide_.backPointerResponse(createEchoArgsList(arg)); + + core.close(dataPipe.producerHandle); + core.close(messagePipe.handle0); + iteration += 1; + } + + proto.accept = proto.realAccept; + proto.realAccept = null; + this.cppSide_.testFinished(); + }; + + function writeDataPipe(pipe, data) { + var writeResult = core.writeData( + pipe.producerHandle, data, core.WRITE_DATA_FLAG_ALL_OR_NONE); + + if (writeResult.result != core.RESULT_OK) { + console.log('ERROR: Data pipe write result was ' + writeResult.result); + return false; + } + if (writeResult.numBytes != data.length) { + console.log('ERROR: Data pipe write length was ' + writeResult.numBytes); + return false; + } + return true; + } + + function writeMessagePipe(pipe, arrayBuffer) { + var result = core.writeMessage(pipe.handle0, arrayBuffer, [], 0); + if (result != core.RESULT_OK) { + console.log('ERROR: Message pipe write result was ' + result); + return false; + } + return true; + } + + function createEchoArgsListElement(item, next) { + var list = new jsToCpp.EchoArgsList(); + list.item = item; + list.next = next; + return list; + } + + function createEchoArgsList() { + var genuineArray = Array.prototype.slice.call(arguments); + return genuineArray.reduceRight(function (previous, current) { + return createEchoArgsListElement(current, previous); + }, null); + } + + return function(jsSideRequestHandle) { + var i; + sampleData = new Uint8Array(DATA_PIPE_PARAMS.capacityNumBytes); + for (i = 0; i < sampleData.length; ++i) { + sampleData[i] = i; + } + sampleMessage = new Uint8Array(DATA_PIPE_PARAMS.capacityNumBytes); + for (i = 0; i < sampleMessage.length; ++i) { + sampleMessage[i] = 255 - i; + } + retainedJsSide = new JsSideConnection; + retainedJsSide.binding.bind(jsSideRequestHandle); + }; +}); diff --git a/mojo/edk/js/tests/run_js_unittests.cc b/mojo/edk/js/tests/run_js_unittests.cc new file mode 100644 index 0000000000000000000000000000000000000000..13e796ba62ba541b6361120eeb7c1254d831bbad --- /dev/null +++ b/mojo/edk/js/tests/run_js_unittests.cc @@ -0,0 +1,61 @@ +// Copyright 2014 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. + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/path_service.h" +#include "gin/modules/console.h" +#include "gin/modules/module_registry.h" +#include "gin/modules/timer.h" +#include "gin/test/file_runner.h" +#include "gin/test/gtest.h" +#include "mojo/edk/js/core.h" +#include "mojo/edk/js/support.h" +#include "mojo/edk/js/threading.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace js { +namespace { + +class TestRunnerDelegate : public gin::FileRunnerDelegate { + public: + TestRunnerDelegate() { + AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule); + AddBuiltinModule(gin::TimerModule::kName, gin::TimerModule::GetModule); + AddBuiltinModule(Core::kModuleName, Core::GetModule); + AddBuiltinModule(Threading::kModuleName, Threading::GetModule); + AddBuiltinModule(Support::kModuleName, Support::GetModule); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestRunnerDelegate); +}; + +void RunTest(std::string test, bool run_until_idle) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.AppendASCII("mojo") + .AppendASCII("public") + .AppendASCII("js") + .AppendASCII("tests") + .AppendASCII(test); + TestRunnerDelegate delegate; + gin::RunTestFromFile(path, &delegate, run_until_idle); +} + +// TODO(abarth): Should we autogenerate these stubs from GYP? +TEST(JSTest, Core) { + RunTest("core_unittest.js", true); +} + +TEST(JSTest, Validation) { + RunTest("validation_unittest.js", true); +} + +} // namespace +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/threading.cc b/mojo/edk/js/threading.cc new file mode 100644 index 0000000000000000000000000000000000000000..db9f12d3d58b3c8f72fe666cc25c1607eab6f03a --- /dev/null +++ b/mojo/edk/js/threading.cc @@ -0,0 +1,49 @@ +// Copyright 2013 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. + +#include "mojo/edk/js/threading.h" + +#include "base/message_loop/message_loop.h" +#include "gin/object_template_builder.h" +#include "gin/per_isolate_data.h" +#include "mojo/edk/js/handle.h" + +namespace mojo { +namespace edk { +namespace js { + +namespace { + +void Quit() { + base::MessageLoop::current()->QuitNow(); +} + +gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin }; + +} // namespace + +const char Threading::kModuleName[] = "mojo/public/js/threading"; + +v8::Local Threading::GetModule(v8::Isolate* isolate) { + gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); + v8::Local templ = data->GetObjectTemplate( + &g_wrapper_info); + + if (templ.IsEmpty()) { + templ = gin::ObjectTemplateBuilder(isolate) + .SetMethod("quit", Quit) + .Build(); + + data->SetObjectTemplate(&g_wrapper_info, templ); + } + + return templ->NewInstance(); +} + +Threading::Threading() { +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/threading.h b/mojo/edk/js/threading.h new file mode 100644 index 0000000000000000000000000000000000000000..653d0765a6971f407abe3c51d282b4bf58772eba --- /dev/null +++ b/mojo/edk/js/threading.h @@ -0,0 +1,28 @@ +// Copyright 2013 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 MOJO_EDK_JS_THREADING_H_ +#define MOJO_EDK_JS_THREADING_H_ + +#include "gin/public/wrapper_info.h" +#include "mojo/edk/js/js_export.h" +#include "v8/include/v8.h" + +namespace mojo { +namespace edk { +namespace js { + +class MOJO_JS_EXPORT Threading { + public: + static const char kModuleName[]; + static v8::Local GetModule(v8::Isolate* isolate); + private: + Threading(); +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_THREADING_H_ diff --git a/mojo/edk/js/waiting_callback.cc b/mojo/edk/js/waiting_callback.cc new file mode 100644 index 0000000000000000000000000000000000000000..6ad4bd034711f7c177561612029cb39e179ecf84 --- /dev/null +++ b/mojo/edk/js/waiting_callback.cc @@ -0,0 +1,95 @@ +// Copyright 2014 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. + +#include "mojo/edk/js/waiting_callback.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "gin/per_context_data.h" + +namespace mojo { +namespace edk { +namespace js { + +namespace { + +v8::Handle GetHiddenPropertyName(v8::Isolate* isolate) { + return v8::Private::ForApi( + isolate, gin::StringToV8(isolate, "::mojo::js::WaitingCallback")); +} + +} // namespace + +gin::WrapperInfo WaitingCallback::kWrapperInfo = { gin::kEmbedderNativeGin }; + +// static +gin::Handle WaitingCallback::Create( + v8::Isolate* isolate, + v8::Handle callback, + gin::Handle handle_wrapper, + MojoHandleSignals signals, + bool one_shot) { + gin::Handle waiting_callback = gin::CreateHandle( + isolate, new WaitingCallback(isolate, callback, one_shot)); + MojoResult result = waiting_callback->watcher_.Watch( + handle_wrapper->get(), signals, + base::Bind(&WaitingCallback::OnHandleReady, + base::Unretained(waiting_callback.get()))); + + // The signals may already be unsatisfiable. + if (result == MOJO_RESULT_FAILED_PRECONDITION) + waiting_callback->OnHandleReady(MOJO_RESULT_FAILED_PRECONDITION); + + return waiting_callback; +} + +void WaitingCallback::Cancel() { + if (watcher_.IsWatching()) + watcher_.Cancel(); +} + +WaitingCallback::WaitingCallback(v8::Isolate* isolate, + v8::Handle callback, + bool one_shot) + : one_shot_(one_shot), + watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC), + weak_factory_(this) { + v8::Handle context = isolate->GetCurrentContext(); + runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr(); + GetWrapper(isolate) + ->SetPrivate(context, GetHiddenPropertyName(isolate), callback) + .FromJust(); +} + +WaitingCallback::~WaitingCallback() { + Cancel(); +} + +void WaitingCallback::OnHandleReady(MojoResult result) { + if (!runner_) + return; + + gin::Runner::Scope scope(runner_.get()); + v8::Isolate* isolate = runner_->GetContextHolder()->isolate(); + + v8::Handle hidden_value = + GetWrapper(isolate) + ->GetPrivate(runner_->GetContextHolder()->context(), + GetHiddenPropertyName(isolate)) + .ToLocalChecked(); + v8::Handle callback; + CHECK(gin::ConvertFromV8(isolate, hidden_value, &callback)); + + v8::Handle args[] = { gin::ConvertToV8(isolate, result) }; + runner_->Call(callback, runner_->global(), 1, args); + + if (one_shot_ || result == MOJO_RESULT_CANCELLED) { + runner_.reset(); + Cancel(); + } +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/waiting_callback.h b/mojo/edk/js/waiting_callback.h new file mode 100644 index 0000000000000000000000000000000000000000..f97b389aea105f84a4935b9abeef31a250e140e1 --- /dev/null +++ b/mojo/edk/js/waiting_callback.h @@ -0,0 +1,67 @@ +// Copyright 2014 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 MOJO_EDK_JS_WAITING_CALLBACK_H_ +#define MOJO_EDK_JS_WAITING_CALLBACK_H_ + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "gin/handle.h" +#include "gin/runner.h" +#include "gin/wrappable.h" +#include "mojo/edk/js/handle.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/simple_watcher.h" + +namespace mojo { +namespace edk { +namespace js { + +class WaitingCallback : public gin::Wrappable { + public: + static gin::WrapperInfo kWrapperInfo; + + // Creates a new WaitingCallback. + // + // If |one_shot| is true, the callback will only ever be called at most once. + // If false, the callback may be called any number of times until the + // WaitingCallback is explicitly cancelled. + static gin::Handle Create( + v8::Isolate* isolate, + v8::Handle callback, + gin::Handle handle_wrapper, + MojoHandleSignals signals, + bool one_shot); + + // Cancels the callback. Does nothing if a callback is not pending. This is + // implicitly invoked from the destructor but can be explicitly invoked as + // necessary. + void Cancel(); + + private: + WaitingCallback(v8::Isolate* isolate, + v8::Handle callback, + bool one_shot); + ~WaitingCallback() override; + + // Callback from the Watcher. + void OnHandleReady(MojoResult result); + + // Indicates whether this is a one-shot callback or not. If so, it uses the + // deprecated HandleWatcher to wait for signals; otherwise it uses the new + // system Watcher API. + const bool one_shot_; + + base::WeakPtr runner_; + SimpleWatcher watcher_; + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(WaitingCallback); +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_WAITING_CALLBACK_H_ diff --git a/mojo/edk/system/BUILD.gn b/mojo/edk/system/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..a68cd44ff1f80ef5236674e8fc35c8238a1d933c --- /dev/null +++ b/mojo/edk/system/BUILD.gn @@ -0,0 +1,205 @@ +# Copyright 2014 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/nacl/config.gni") +import("//testing/test.gni") +import("../../../mojo/public/tools/bindings/mojom.gni") + +if (is_android) { + import("//build/config/android/config.gni") + import("//build/config/android/rules.gni") +} + +component("system") { + output_name = "mojo_system_impl" + + sources = [ + "atomic_flag.h", + "broker.h", + "broker_host.cc", + "broker_host.h", + "broker_posix.cc", + "broker_win.cc", + "channel.cc", + "channel.h", + "channel_posix.cc", + "channel_win.cc", + "configuration.cc", + "configuration.h", + "core.cc", + "core.h", + "data_pipe_consumer_dispatcher.cc", + "data_pipe_consumer_dispatcher.h", + "data_pipe_control_message.cc", + "data_pipe_control_message.h", + "data_pipe_producer_dispatcher.cc", + "data_pipe_producer_dispatcher.h", + "dispatcher.cc", + "dispatcher.h", + "handle_signals_state.h", + "handle_table.cc", + "handle_table.h", + "mapping_table.cc", + "mapping_table.h", + "message_for_transit.cc", + "message_for_transit.h", + "message_pipe_dispatcher.cc", + "message_pipe_dispatcher.h", + "node_channel.cc", + "node_channel.h", + "node_controller.cc", + "node_controller.h", + "options_validation.h", + "platform_handle_dispatcher.cc", + "platform_handle_dispatcher.h", + "ports_message.cc", + "ports_message.h", + "request_context.cc", + "request_context.h", + "shared_buffer_dispatcher.cc", + "shared_buffer_dispatcher.h", + "watch.cc", + "watch.h", + "watcher_dispatcher.cc", + "watcher_dispatcher.h", + "watcher_set.cc", + "watcher_set.h", + ] + + defines = [ "MOJO_SYSTEM_IMPL_IMPLEMENTATION" ] + + public_deps = [ + "//mojo/edk/embedder", + "//mojo/edk/embedder:platform", + "//mojo/edk/system/ports", + "//mojo/public/c/system", + "//mojo/public/cpp/system", + ] + + deps = [ + "//base", + ] + + if (!is_nacl) { + deps += [ "//crypto" ] + } + + if (is_win) { + cflags = [ "/wd4324" ] # Structure was padded due to __declspec(align()), + # which is uninteresting. + } + + if (is_mac && !is_ios) { + sources += [ + "mach_port_relay.cc", + "mach_port_relay.h", + ] + } + + if (is_nacl && !is_nacl_nonsfi) { + sources -= [ + "broker_host.cc", + "broker_posix.cc", + "channel_posix.cc", + ] + } + + # Use target_os == "chromeos" instead of is_chromeos because we need to + # build NaCl targets (i.e. IRT) for ChromeOS the same as the rest of ChromeOS. + if (is_android || target_os == "chromeos") { + defines += [ "MOJO_EDK_LEGACY_PROTOCOL" ] + } + + allow_circular_includes_from = [ "//mojo/edk/embedder" ] +} + +group("tests") { + testonly = true + deps = [ + ":mojo_system_unittests", + ] + + if (!is_ios) { + deps += [ ":mojo_message_pipe_perftests" ] + } +} + +source_set("test_utils") { + testonly = true + + sources = [ + "test_utils.cc", + "test_utils.h", + ] + + public_deps = [ + "//mojo/public/c/system", + "//mojo/public/cpp/system", + ] + + deps = [ + "//base", + "//base/test:test_support", + "//mojo/edk/test:test_support", + "//testing/gtest:gtest", + ] +} + +test("mojo_system_unittests") { + sources = [ + "channel_unittest.cc", + "core_test_base.cc", + "core_test_base.h", + "core_unittest.cc", + "message_pipe_unittest.cc", + "options_validation_unittest.cc", + "platform_handle_dispatcher_unittest.cc", + "shared_buffer_dispatcher_unittest.cc", + "shared_buffer_unittest.cc", + "signals_unittest.cc", + "watcher_unittest.cc", + ] + + if (!is_ios) { + sources += [ + "data_pipe_unittest.cc", + "multiprocess_message_pipe_unittest.cc", + "platform_wrapper_unittest.cc", + ] + } + + deps = [ + ":test_utils", + "//base", + "//base/test:test_support", + "//mojo/edk/embedder:embedder_unittests", + "//mojo/edk/system", + "//mojo/edk/system/ports:tests", + "//mojo/edk/test:run_all_unittests", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/system", + "//testing/gmock", + "//testing/gtest", + ] + + allow_circular_includes_from = [ "//mojo/edk/embedder:embedder_unittests" ] +} + +if (!is_ios) { + test("mojo_message_pipe_perftests") { + sources = [ + "message_pipe_perftest.cc", + ] + + deps = [ + ":test_utils", + "//base", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/edk/test:run_all_perftests", + "//mojo/edk/test:test_support", + "//testing/gtest", + ] + } +} diff --git a/mojo/edk/system/atomic_flag.h b/mojo/edk/system/atomic_flag.h new file mode 100644 index 0000000000000000000000000000000000000000..6bdcfaaddd9e40559326d1bce597dc8fa6f18b4d --- /dev/null +++ b/mojo/edk/system/atomic_flag.h @@ -0,0 +1,57 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_ATOMIC_FLAG_H_ +#define MOJO_EDK_SYSTEM_ATOMIC_FLAG_H_ + +#include "base/atomicops.h" +#include "base/macros.h" + +namespace mojo { +namespace edk { + +// AtomicFlag is a boolean flag that can be set and tested atomically. It is +// intended to be used to fast-path checks where the common case would normally +// release the governing mutex immediately after checking. +// +// Example usage: +// void DoFoo(Bar* bar) { +// AutoLock l(lock_); +// queue_.push_back(bar); +// flag_.Set(true); +// } +// +// void Baz() { +// if (!flag_) // Assume this is the common case. +// return; +// +// AutoLock l(lock_); +// ... drain queue_ ... +// flag_.Set(false); +// } +class AtomicFlag { + public: + AtomicFlag() : flag_(0) {} + ~AtomicFlag() {} + + void Set(bool value) { + base::subtle::Release_Store(&flag_, value ? 1 : 0); + } + + bool Get() const { + return base::subtle::Acquire_Load(&flag_) ? true : false; + } + + operator const bool() const { return Get(); } + + private: + base::subtle::Atomic32 flag_; + + DISALLOW_COPY_AND_ASSIGN(AtomicFlag); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_ATOMIC_FLAG_H_ diff --git a/mojo/edk/system/broker.h b/mojo/edk/system/broker.h new file mode 100644 index 0000000000000000000000000000000000000000..1577972a6d43af78de6d1823126aafa33a97f642 --- /dev/null +++ b/mojo/edk/system/broker.h @@ -0,0 +1,52 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_BROKER_H_ +#define MOJO_EDK_SYSTEM_BROKER_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" + +namespace mojo { +namespace edk { + +class PlatformSharedBuffer; + +// The Broker is a channel to the parent process, which allows synchronous IPCs. +class Broker { + public: + // Note: This is blocking, and will wait for the first message over + // |platform_handle|. + explicit Broker(ScopedPlatformHandle platform_handle); + ~Broker(); + + // Returns the platform handle that should be used to establish a NodeChannel + // to the parent process. + ScopedPlatformHandle GetParentPlatformHandle(); + + // Request a shared buffer from the parent process. Blocks the current thread. + scoped_refptr GetSharedBuffer(size_t num_bytes); + + private: + // Handle to the parent process, used for synchronous IPCs. + ScopedPlatformHandle sync_channel_; + + // Handle to the parent process which is recieved in the first first message + // over |sync_channel_|. + ScopedPlatformHandle parent_channel_; + + // Lock to only allow one sync message at a time. This avoids having to deal + // with message ordering since we can only have one request at a time + // in-flight. + base::Lock lock_; + + DISALLOW_COPY_AND_ASSIGN(Broker); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_BROKER_H_ diff --git a/mojo/edk/system/broker_host.cc b/mojo/edk/system/broker_host.cc new file mode 100644 index 0000000000000000000000000000000000000000..6096034fa2ade820de10437e22a5ba840c8cfde2 --- /dev/null +++ b/mojo/edk/system/broker_host.cc @@ -0,0 +1,153 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/broker_host.h" + +#include + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/edk/embedder/named_platform_channel_pair.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/broker_messages.h" + +namespace mojo { +namespace edk { + +BrokerHost::BrokerHost(base::ProcessHandle client_process, + ScopedPlatformHandle platform_handle) +#if defined(OS_WIN) + : client_process_(client_process) +#endif +{ + CHECK(platform_handle.is_valid()); + + base::MessageLoop::current()->AddDestructionObserver(this); + + channel_ = Channel::Create(this, ConnectionParams(std::move(platform_handle)), + base::ThreadTaskRunnerHandle::Get()); + channel_->Start(); +} + +BrokerHost::~BrokerHost() { + // We're always destroyed on the creation thread, which is the IO thread. + base::MessageLoop::current()->RemoveDestructionObserver(this); + + if (channel_) + channel_->ShutDown(); +} + +bool BrokerHost::PrepareHandlesForClient(PlatformHandleVector* handles) { +#if defined(OS_WIN) + if (!Channel::Message::RewriteHandles( + base::GetCurrentProcessHandle(), client_process_, handles)) { + // NOTE: We only log an error here. We do not signal a logical error or + // prevent any message from being sent. The client should handle unexpected + // invalid handles appropriately. + DLOG(ERROR) << "Failed to rewrite one or more handles to broker client."; + return false; + } +#endif + return true; +} + +bool BrokerHost::SendChannel(ScopedPlatformHandle handle) { + CHECK(handle.is_valid()); + CHECK(channel_); + +#if defined(OS_WIN) + InitData* data; + Channel::MessagePtr message = + CreateBrokerMessage(BrokerMessageType::INIT, 1, 0, &data); + data->pipe_name_length = 0; +#else + Channel::MessagePtr message = + CreateBrokerMessage(BrokerMessageType::INIT, 1, nullptr); +#endif + ScopedPlatformHandleVectorPtr handles; + handles.reset(new PlatformHandleVector(1)); + handles->at(0) = handle.release(); + + // This may legitimately fail on Windows if the client process is in another + // session, e.g., is an elevated process. + if (!PrepareHandlesForClient(handles.get())) + return false; + + message->SetHandles(std::move(handles)); + channel_->Write(std::move(message)); + return true; +} + +#if defined(OS_WIN) + +void BrokerHost::SendNamedChannel(const base::StringPiece16& pipe_name) { + InitData* data; + base::char16* name_data; + Channel::MessagePtr message = CreateBrokerMessage( + BrokerMessageType::INIT, 0, sizeof(*name_data) * pipe_name.length(), + &data, reinterpret_cast(&name_data)); + data->pipe_name_length = static_cast(pipe_name.length()); + std::copy(pipe_name.begin(), pipe_name.end(), name_data); + channel_->Write(std::move(message)); +} + +#endif // defined(OS_WIN) + +void BrokerHost::OnBufferRequest(uint32_t num_bytes) { + scoped_refptr read_only_buffer; + scoped_refptr buffer = + PlatformSharedBuffer::Create(num_bytes); + if (buffer) + read_only_buffer = buffer->CreateReadOnlyDuplicate(); + if (!read_only_buffer) + buffer = nullptr; + + Channel::MessagePtr message = CreateBrokerMessage( + BrokerMessageType::BUFFER_RESPONSE, buffer ? 2 : 0, nullptr); + if (buffer) { + ScopedPlatformHandleVectorPtr handles; + handles.reset(new PlatformHandleVector(2)); + handles->at(0) = buffer->PassPlatformHandle().release(); + handles->at(1) = read_only_buffer->PassPlatformHandle().release(); + PrepareHandlesForClient(handles.get()); + message->SetHandles(std::move(handles)); + } + + channel_->Write(std::move(message)); +} + +void BrokerHost::OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) { + if (payload_size < sizeof(BrokerMessageHeader)) + return; + + const BrokerMessageHeader* header = + static_cast(payload); + switch (header->type) { + case BrokerMessageType::BUFFER_REQUEST: + if (payload_size == + sizeof(BrokerMessageHeader) + sizeof(BufferRequestData)) { + const BufferRequestData* request = + reinterpret_cast(header + 1); + OnBufferRequest(request->size); + } + break; + + default: + LOG(ERROR) << "Unexpected broker message type: " << header->type; + break; + } +} + +void BrokerHost::OnChannelError() { delete this; } + +void BrokerHost::WillDestroyCurrentMessageLoop() { delete this; } + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/broker_host.h b/mojo/edk/system/broker_host.h new file mode 100644 index 0000000000000000000000000000000000000000..a7995d2b0fcdd42677e0b9e47e08ba6a0188886b --- /dev/null +++ b/mojo/edk/system/broker_host.h @@ -0,0 +1,64 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_BROKER_HOST_H_ +#define MOJO_EDK_SYSTEM_BROKER_HOST_H_ + +#include + +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/process/process_handle.h" +#include "base/strings/string_piece.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/channel.h" + +namespace mojo { +namespace edk { + +// The BrokerHost is a channel to the child process, which services synchronous +// IPCs. +class BrokerHost : public Channel::Delegate, + public base::MessageLoop::DestructionObserver { + public: + BrokerHost(base::ProcessHandle client_process, ScopedPlatformHandle handle); + + // Send |handle| to the child, to be used to establish a NodeChannel to us. + bool SendChannel(ScopedPlatformHandle handle); + +#if defined(OS_WIN) + // Sends a named channel to the child. Like above, but for named pipes. + void SendNamedChannel(const base::StringPiece16& pipe_name); +#endif + + private: + ~BrokerHost() override; + + bool PrepareHandlesForClient(PlatformHandleVector* handles); + + // Channel::Delegate: + void OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) override; + void OnChannelError() override; + + // base::MessageLoop::DestructionObserver: + void WillDestroyCurrentMessageLoop() override; + + void OnBufferRequest(uint32_t num_bytes); + +#if defined(OS_WIN) + base::ProcessHandle client_process_; +#endif + + scoped_refptr channel_; + + DISALLOW_COPY_AND_ASSIGN(BrokerHost); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_BROKER_HOST_H_ diff --git a/mojo/edk/system/broker_messages.h b/mojo/edk/system/broker_messages.h new file mode 100644 index 0000000000000000000000000000000000000000..0f0dd9dc42b11aa5ba52de63373de86a34dc7d20 --- /dev/null +++ b/mojo/edk/system/broker_messages.h @@ -0,0 +1,80 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_BROKER_MESSAGES_H_ +#define MOJO_EDK_SYSTEM_BROKER_MESSAGES_H_ + +#include "mojo/edk/system/channel.h" + +namespace mojo { +namespace edk { + +#pragma pack(push, 1) + +enum BrokerMessageType : uint32_t { + INIT, + BUFFER_REQUEST, + BUFFER_RESPONSE, +}; + +struct BrokerMessageHeader { + BrokerMessageType type; + uint32_t padding; +}; + +static_assert(IsAlignedForChannelMessage(sizeof(BrokerMessageHeader)), + "Invalid header size."); + +struct BufferRequestData { + uint32_t size; +}; + +#if defined(OS_WIN) +struct InitData { + // NOTE: InitData in the payload is followed by string16 data with exactly + // |pipe_name_length| wide characters (i.e., |pipe_name_length|*2 bytes.) + // This applies to Windows only. + uint32_t pipe_name_length; +}; +#endif + +#pragma pack(pop) + +template +inline Channel::MessagePtr CreateBrokerMessage( + BrokerMessageType type, + size_t num_handles, + size_t extra_data_size, + T** out_message_data, + void** out_extra_data = nullptr) { + const size_t message_size = sizeof(BrokerMessageHeader) + + sizeof(**out_message_data) + extra_data_size; + Channel::MessagePtr message(new Channel::Message(message_size, num_handles)); + BrokerMessageHeader* header = + reinterpret_cast(message->mutable_payload()); + header->type = type; + header->padding = 0; + *out_message_data = reinterpret_cast(header + 1); + if (out_extra_data) + *out_extra_data = *out_message_data + 1; + return message; +} + +inline Channel::MessagePtr CreateBrokerMessage( + BrokerMessageType type, + size_t num_handles, + std::nullptr_t** dummy_out_data) { + Channel::MessagePtr message( + new Channel::Message(sizeof(BrokerMessageHeader), num_handles)); + BrokerMessageHeader* header = + reinterpret_cast(message->mutable_payload()); + header->type = type; + header->padding = 0; + return message; +} + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_BROKER_MESSAGES_H_ diff --git a/mojo/edk/system/broker_posix.cc b/mojo/edk/system/broker_posix.cc new file mode 100644 index 0000000000000000000000000000000000000000..8742f709a7bc8be5bce08f357de30de8afaf5caf --- /dev/null +++ b/mojo/edk/system/broker_posix.cc @@ -0,0 +1,125 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/broker.h" + +#include +#include + +#include + +#include "base/logging.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/platform_channel_utils_posix.h" +#include "mojo/edk/embedder/platform_handle_utils.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/broker_messages.h" +#include "mojo/edk/system/channel.h" + +namespace mojo { +namespace edk { + +namespace { + +bool WaitForBrokerMessage(PlatformHandle platform_handle, + BrokerMessageType expected_type, + size_t expected_num_handles, + std::deque* incoming_handles) { + Channel::MessagePtr message( + new Channel::Message(sizeof(BrokerMessageHeader), expected_num_handles)); + std::deque incoming_platform_handles; + ssize_t read_result = PlatformChannelRecvmsg( + platform_handle, const_cast(message->data()), + message->data_num_bytes(), &incoming_platform_handles, true /* block */); + bool error = false; + if (read_result < 0) { + PLOG(ERROR) << "Recvmsg error"; + error = true; + } else if (static_cast(read_result) != message->data_num_bytes()) { + LOG(ERROR) << "Invalid node channel message"; + error = true; + } else if (incoming_platform_handles.size() != expected_num_handles) { + LOG(ERROR) << "Received unexpected number of handles"; + error = true; + } + + if (!error) { + const BrokerMessageHeader* header = + reinterpret_cast(message->payload()); + if (header->type != expected_type) { + LOG(ERROR) << "Unexpected message"; + error = true; + } + } + + if (error) { + CloseAllPlatformHandles(&incoming_platform_handles); + } else { + if (incoming_handles) + incoming_handles->swap(incoming_platform_handles); + } + return !error; +} + +} // namespace + +Broker::Broker(ScopedPlatformHandle platform_handle) + : sync_channel_(std::move(platform_handle)) { + CHECK(sync_channel_.is_valid()); + + // Mark the channel as blocking. + int flags = fcntl(sync_channel_.get().handle, F_GETFL); + PCHECK(flags != -1); + flags = fcntl(sync_channel_.get().handle, F_SETFL, flags & ~O_NONBLOCK); + PCHECK(flags != -1); + + // Wait for the first message, which should contain a handle. + std::deque incoming_platform_handles; + if (WaitForBrokerMessage(sync_channel_.get(), BrokerMessageType::INIT, 1, + &incoming_platform_handles)) { + parent_channel_ = ScopedPlatformHandle(incoming_platform_handles.front()); + } +} + +Broker::~Broker() = default; + +ScopedPlatformHandle Broker::GetParentPlatformHandle() { + return std::move(parent_channel_); +} + +scoped_refptr Broker::GetSharedBuffer(size_t num_bytes) { + base::AutoLock lock(lock_); + + BufferRequestData* buffer_request; + Channel::MessagePtr out_message = CreateBrokerMessage( + BrokerMessageType::BUFFER_REQUEST, 0, 0, &buffer_request); + buffer_request->size = num_bytes; + ssize_t write_result = PlatformChannelWrite( + sync_channel_.get(), out_message->data(), out_message->data_num_bytes()); + if (write_result < 0) { + PLOG(ERROR) << "Error sending sync broker message"; + return nullptr; + } else if (static_cast(write_result) != + out_message->data_num_bytes()) { + LOG(ERROR) << "Error sending complete broker message"; + return nullptr; + } + + std::deque incoming_platform_handles; + if (WaitForBrokerMessage(sync_channel_.get(), + BrokerMessageType::BUFFER_RESPONSE, 2, + &incoming_platform_handles)) { + ScopedPlatformHandle rw_handle(incoming_platform_handles.front()); + incoming_platform_handles.pop_front(); + ScopedPlatformHandle ro_handle(incoming_platform_handles.front()); + return PlatformSharedBuffer::CreateFromPlatformHandlePair( + num_bytes, std::move(rw_handle), std::move(ro_handle)); + } + + return nullptr; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/broker_win.cc b/mojo/edk/system/broker_win.cc new file mode 100644 index 0000000000000000000000000000000000000000..063282c146f9045d14f9653d9a44df67b5b8ba29 --- /dev/null +++ b/mojo/edk/system/broker_win.cc @@ -0,0 +1,155 @@ +// Copyright 2016 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. + +#include + +#include +#include + +#include "base/debug/alias.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_piece.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/named_platform_handle_utils.h" +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/broker.h" +#include "mojo/edk/system/broker_messages.h" +#include "mojo/edk/system/channel.h" + +namespace mojo { +namespace edk { + +namespace { + +// 256 bytes should be enough for anyone! +const size_t kMaxBrokerMessageSize = 256; + +bool TakeHandlesFromBrokerMessage(Channel::Message* message, + size_t num_handles, + ScopedPlatformHandle* out_handles) { + if (message->num_handles() != num_handles) { + DLOG(ERROR) << "Received unexpected number of handles in broker message"; + return false; + } + + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + DCHECK(handles); + DCHECK_EQ(handles->size(), num_handles); + DCHECK(out_handles); + + for (size_t i = 0; i < num_handles; ++i) + out_handles[i] = ScopedPlatformHandle((*handles)[i]); + handles->clear(); + return true; +} + +Channel::MessagePtr WaitForBrokerMessage(PlatformHandle platform_handle, + BrokerMessageType expected_type) { + char buffer[kMaxBrokerMessageSize]; + DWORD bytes_read = 0; + BOOL result = ::ReadFile(platform_handle.handle, buffer, + kMaxBrokerMessageSize, &bytes_read, nullptr); + if (!result) { + // The pipe may be broken if the browser side has been closed, e.g. during + // browser shutdown. In that case the ReadFile call will fail and we + // shouldn't continue waiting. + PLOG(ERROR) << "Error reading broker pipe"; + return nullptr; + } + + Channel::MessagePtr message = + Channel::Message::Deserialize(buffer, static_cast(bytes_read)); + if (!message || message->payload_size() < sizeof(BrokerMessageHeader)) { + LOG(ERROR) << "Invalid broker message"; + + base::debug::Alias(&buffer[0]); + base::debug::Alias(&bytes_read); + base::debug::Alias(message.get()); + CHECK(false); + return nullptr; + } + + const BrokerMessageHeader* header = + reinterpret_cast(message->payload()); + if (header->type != expected_type) { + LOG(ERROR) << "Unexpected broker message type"; + + base::debug::Alias(&buffer[0]); + base::debug::Alias(&bytes_read); + base::debug::Alias(message.get()); + CHECK(false); + return nullptr; + } + + return message; +} + +} // namespace + +Broker::Broker(ScopedPlatformHandle handle) : sync_channel_(std::move(handle)) { + CHECK(sync_channel_.is_valid()); + Channel::MessagePtr message = + WaitForBrokerMessage(sync_channel_.get(), BrokerMessageType::INIT); + + // If we fail to read a message (broken pipe), just return early. The parent + // handle will be null and callers must handle this gracefully. + if (!message) + return; + + if (!TakeHandlesFromBrokerMessage(message.get(), 1, &parent_channel_)) { + // If the message has no handles, we expect it to carry pipe name instead. + const BrokerMessageHeader* header = + static_cast(message->payload()); + CHECK_GE(message->payload_size(), + sizeof(BrokerMessageHeader) + sizeof(InitData)); + const InitData* data = reinterpret_cast(header + 1); + CHECK_EQ(message->payload_size(), + sizeof(BrokerMessageHeader) + sizeof(InitData) + + data->pipe_name_length * sizeof(base::char16)); + const base::char16* name_data = + reinterpret_cast(data + 1); + CHECK(data->pipe_name_length); + parent_channel_ = CreateClientHandle(NamedPlatformHandle( + base::StringPiece16(name_data, data->pipe_name_length))); + } +} + +Broker::~Broker() {} + +ScopedPlatformHandle Broker::GetParentPlatformHandle() { + return std::move(parent_channel_); +} + +scoped_refptr Broker::GetSharedBuffer(size_t num_bytes) { + base::AutoLock lock(lock_); + BufferRequestData* buffer_request; + Channel::MessagePtr out_message = CreateBrokerMessage( + BrokerMessageType::BUFFER_REQUEST, 0, 0, &buffer_request); + buffer_request->size = base::checked_cast(num_bytes); + DWORD bytes_written = 0; + BOOL result = ::WriteFile(sync_channel_.get().handle, out_message->data(), + static_cast(out_message->data_num_bytes()), + &bytes_written, nullptr); + if (!result || + static_cast(bytes_written) != out_message->data_num_bytes()) { + LOG(ERROR) << "Error sending sync broker message"; + return nullptr; + } + + ScopedPlatformHandle handles[2]; + Channel::MessagePtr response = WaitForBrokerMessage( + sync_channel_.get(), BrokerMessageType::BUFFER_RESPONSE); + if (response && + TakeHandlesFromBrokerMessage(response.get(), 2, &handles[0])) { + return PlatformSharedBuffer::CreateFromPlatformHandlePair( + num_bytes, std::move(handles[0]), std::move(handles[1])); + } + + return nullptr; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/channel.cc b/mojo/edk/system/channel.cc new file mode 100644 index 0000000000000000000000000000000000000000..8a44d36024e2346b3eab90a48f1eb77a35adcd63 --- /dev/null +++ b/mojo/edk/system/channel.cc @@ -0,0 +1,683 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/channel.h" + +#include +#include + +#include +#include +#include + +#include "base/macros.h" +#include "base/memory/aligned_memory.h" +#include "base/process/process_handle.h" +#include "mojo/edk/embedder/platform_handle.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "base/mac/mach_logging.h" +#elif defined(OS_WIN) +#include "base/win/win_util.h" +#endif + +namespace mojo { +namespace edk { + +namespace { + +static_assert( + IsAlignedForChannelMessage(sizeof(Channel::Message::LegacyHeader)), + "Invalid LegacyHeader size."); + +static_assert(IsAlignedForChannelMessage(sizeof(Channel::Message::Header)), + "Invalid Header size."); + +static_assert(sizeof(Channel::Message::LegacyHeader) == 8, + "LegacyHeader must be 8 bytes on ChromeOS and Android"); + +static_assert(offsetof(Channel::Message::LegacyHeader, num_bytes) == + offsetof(Channel::Message::Header, num_bytes), + "num_bytes should be at the same offset in both Header structs."); +static_assert(offsetof(Channel::Message::LegacyHeader, message_type) == + offsetof(Channel::Message::Header, message_type), + "message_type should be at the same offset in both Header " + "structs."); + +} // namespace + +const size_t kReadBufferSize = 4096; +const size_t kMaxUnusedReadBufferCapacity = 4096; +const size_t kMaxChannelMessageSize = 256 * 1024 * 1024; +const size_t kMaxAttachedHandles = 128; + +Channel::Message::Message(size_t payload_size, size_t max_handles) +#if defined(MOJO_EDK_LEGACY_PROTOCOL) + : Message(payload_size, max_handles, MessageType::NORMAL_LEGACY) { +} +#else + : Message(payload_size, max_handles, MessageType::NORMAL) { +} +#endif + +Channel::Message::Message(size_t payload_size, + size_t max_handles, + MessageType message_type) + : max_handles_(max_handles) { + DCHECK_LE(max_handles_, kMaxAttachedHandles); + + const bool is_legacy_message = (message_type == MessageType::NORMAL_LEGACY); + size_t extra_header_size = 0; +#if defined(OS_WIN) + // On Windows we serialize HANDLEs into the extra header space. + extra_header_size = max_handles_ * sizeof(HandleEntry); +#elif defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, some of the platform handles may be mach ports, which are + // serialised into the message buffer. Since there could be a mix of fds and + // mach ports, we store the mach ports as an pair (of uint32_t), + // so that the original ordering of handles can be re-created. + if (max_handles) { + extra_header_size = + sizeof(MachPortsExtraHeader) + (max_handles * sizeof(MachPortsEntry)); + } +#endif + // Pad extra header data to be aliged to |kChannelMessageAlignment| bytes. + if (!IsAlignedForChannelMessage(extra_header_size)) { + extra_header_size += kChannelMessageAlignment - + (extra_header_size % kChannelMessageAlignment); + } + DCHECK(IsAlignedForChannelMessage(extra_header_size)); + const size_t header_size = + is_legacy_message ? sizeof(LegacyHeader) : sizeof(Header); + DCHECK(extra_header_size == 0 || !is_legacy_message); + + size_ = header_size + extra_header_size + payload_size; + data_ = static_cast(base::AlignedAlloc(size_, + kChannelMessageAlignment)); + // Only zero out the header and not the payload. Since the payload is going to + // be memcpy'd, zeroing the payload is unnecessary work and a significant + // performance issue when dealing with large messages. Any sanitizer errors + // complaining about an uninitialized read in the payload area should be + // treated as an error and fixed. + memset(data_, 0, header_size + extra_header_size); + + DCHECK_LE(size_, std::numeric_limits::max()); + legacy_header()->num_bytes = static_cast(size_); + + DCHECK_LE(header_size + extra_header_size, + std::numeric_limits::max()); + legacy_header()->message_type = message_type; + + if (is_legacy_message) { + legacy_header()->num_handles = static_cast(max_handles); + } else { + header()->num_header_bytes = + static_cast(header_size + extra_header_size); + } + + if (max_handles_ > 0) { +#if defined(OS_WIN) + handles_ = reinterpret_cast(mutable_extra_header()); + // Initialize all handles to invalid values. + for (size_t i = 0; i < max_handles_; ++i) + handles_[i].handle = base::win::HandleToUint32(INVALID_HANDLE_VALUE); +#elif defined(OS_MACOSX) && !defined(OS_IOS) + mach_ports_header_ = + reinterpret_cast(mutable_extra_header()); + mach_ports_header_->num_ports = 0; + // Initialize all handles to invalid values. + for (size_t i = 0; i < max_handles_; ++i) { + mach_ports_header_->entries[i] = + {0, static_cast(MACH_PORT_NULL)}; + } +#endif + } +} + +Channel::Message::~Message() { + base::AlignedFree(data_); +} + +// static +Channel::MessagePtr Channel::Message::Deserialize(const void* data, + size_t data_num_bytes) { + if (data_num_bytes < sizeof(LegacyHeader)) + return nullptr; + + const LegacyHeader* legacy_header = + reinterpret_cast(data); + if (legacy_header->num_bytes != data_num_bytes) { + DLOG(ERROR) << "Decoding invalid message: " << legacy_header->num_bytes + << " != " << data_num_bytes; + return nullptr; + } + + const Header* header = nullptr; + if (legacy_header->message_type == MessageType::NORMAL) + header = reinterpret_cast(data); + + uint32_t extra_header_size = 0; + size_t payload_size = 0; + const char* payload = nullptr; + if (!header) { + payload_size = data_num_bytes - sizeof(LegacyHeader); + payload = static_cast(data) + sizeof(LegacyHeader); + } else { + if (header->num_bytes < header->num_header_bytes || + header->num_header_bytes < sizeof(Header)) { + DLOG(ERROR) << "Decoding invalid message: " << header->num_bytes << " < " + << header->num_header_bytes; + return nullptr; + } + extra_header_size = header->num_header_bytes - sizeof(Header); + payload_size = data_num_bytes - header->num_header_bytes; + payload = static_cast(data) + header->num_header_bytes; + } + +#if defined(OS_WIN) + uint32_t max_handles = extra_header_size / sizeof(HandleEntry); +#elif defined(OS_MACOSX) && !defined(OS_IOS) + if (extra_header_size > 0 && + extra_header_size < sizeof(MachPortsExtraHeader)) { + DLOG(ERROR) << "Decoding invalid message: " << extra_header_size << " < " + << sizeof(MachPortsExtraHeader); + return nullptr; + } + uint32_t max_handles = + extra_header_size == 0 + ? 0 + : (extra_header_size - sizeof(MachPortsExtraHeader)) / + sizeof(MachPortsEntry); +#else + const uint32_t max_handles = 0; +#endif // defined(OS_WIN) + + const uint16_t num_handles = + header ? header->num_handles : legacy_header->num_handles; + if (num_handles > max_handles || max_handles > kMaxAttachedHandles) { + DLOG(ERROR) << "Decoding invalid message: " << num_handles << " > " + << max_handles; + return nullptr; + } + + MessagePtr message( + new Message(payload_size, max_handles, legacy_header->message_type)); + DCHECK_EQ(message->data_num_bytes(), data_num_bytes); + + // Copy all payload bytes. + if (payload_size) + memcpy(message->mutable_payload(), payload, payload_size); + + if (header) { + DCHECK_EQ(message->extra_header_size(), extra_header_size); + DCHECK_EQ(message->header()->num_header_bytes, header->num_header_bytes); + + if (message->extra_header_size()) { + // Copy extra header bytes. + memcpy(message->mutable_extra_header(), + static_cast(data) + sizeof(Header), + message->extra_header_size()); + } + message->header()->num_handles = header->num_handles; + } else { + message->legacy_header()->num_handles = legacy_header->num_handles; + } + +#if defined(OS_WIN) + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector(num_handles)); + for (size_t i = 0; i < num_handles; i++) { + (*handles)[i].handle = + base::win::Uint32ToHandle(message->handles_[i].handle); + } + message->SetHandles(std::move(handles)); +#endif + + return message; +} + +const void* Channel::Message::extra_header() const { + DCHECK(!is_legacy_message()); + return data_ + sizeof(Header); +} + +void* Channel::Message::mutable_extra_header() { + DCHECK(!is_legacy_message()); + return data_ + sizeof(Header); +} + +size_t Channel::Message::extra_header_size() const { + return header()->num_header_bytes - sizeof(Header); +} + +void* Channel::Message::mutable_payload() { + if (is_legacy_message()) + return static_cast(legacy_header() + 1); + return data_ + header()->num_header_bytes; +} + +const void* Channel::Message::payload() const { + if (is_legacy_message()) + return static_cast(legacy_header() + 1); + return data_ + header()->num_header_bytes; +} + +size_t Channel::Message::payload_size() const { + if (is_legacy_message()) + return legacy_header()->num_bytes - sizeof(LegacyHeader); + return size_ - header()->num_header_bytes; +} + +size_t Channel::Message::num_handles() const { + return is_legacy_message() ? legacy_header()->num_handles + : header()->num_handles; +} + +bool Channel::Message::has_handles() const { + return (is_legacy_message() ? legacy_header()->num_handles + : header()->num_handles) > 0; +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +bool Channel::Message::has_mach_ports() const { + if (!has_handles()) + return false; + + for (const auto& handle : (*handle_vector_)) { + if (handle.type == PlatformHandle::Type::MACH || + handle.type == PlatformHandle::Type::MACH_NAME) { + return true; + } + } + return false; +} +#endif + +bool Channel::Message::is_legacy_message() const { + return legacy_header()->message_type == MessageType::NORMAL_LEGACY; +} + +Channel::Message::LegacyHeader* Channel::Message::legacy_header() const { + return reinterpret_cast(data_); +} + +Channel::Message::Header* Channel::Message::header() const { + DCHECK(!is_legacy_message()); + return reinterpret_cast(data_); +} + +void Channel::Message::SetHandles(ScopedPlatformHandleVectorPtr new_handles) { + if (is_legacy_message()) { + // Old semantics for ChromeOS and Android + if (legacy_header()->num_handles == 0) { + CHECK(!new_handles || new_handles->size() == 0); + return; + } + CHECK(new_handles && new_handles->size() == legacy_header()->num_handles); + std::swap(handle_vector_, new_handles); + return; + } + + if (max_handles_ == 0) { + CHECK(!new_handles || new_handles->size() == 0); + return; + } + + CHECK(new_handles && new_handles->size() <= max_handles_); + header()->num_handles = static_cast(new_handles->size()); + std::swap(handle_vector_, new_handles); +#if defined(OS_WIN) + memset(handles_, 0, extra_header_size()); + for (size_t i = 0; i < handle_vector_->size(); i++) + handles_[i].handle = base::win::HandleToUint32((*handle_vector_)[i].handle); +#endif // defined(OS_WIN) + +#if defined(OS_MACOSX) && !defined(OS_IOS) + size_t mach_port_index = 0; + if (mach_ports_header_) { + for (size_t i = 0; i < max_handles_; ++i) { + mach_ports_header_->entries[i] = + {0, static_cast(MACH_PORT_NULL)}; + } + for (size_t i = 0; i < handle_vector_->size(); i++) { + if ((*handle_vector_)[i].type == PlatformHandle::Type::MACH || + (*handle_vector_)[i].type == PlatformHandle::Type::MACH_NAME) { + mach_port_t port = (*handle_vector_)[i].port; + mach_ports_header_->entries[mach_port_index].index = i; + mach_ports_header_->entries[mach_port_index].mach_port = port; + mach_port_index++; + } + } + mach_ports_header_->num_ports = static_cast(mach_port_index); + } +#endif +} + +ScopedPlatformHandleVectorPtr Channel::Message::TakeHandles() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + if (mach_ports_header_) { + for (size_t i = 0; i < max_handles_; ++i) { + mach_ports_header_->entries[i] = + {0, static_cast(MACH_PORT_NULL)}; + } + mach_ports_header_->num_ports = 0; + } +#endif + if (is_legacy_message()) + legacy_header()->num_handles = 0; + else + header()->num_handles = 0; + return std::move(handle_vector_); +} + +ScopedPlatformHandleVectorPtr Channel::Message::TakeHandlesForTransport() { +#if defined(OS_WIN) + // Not necessary on Windows. + NOTREACHED(); + return nullptr; +#elif defined(OS_MACOSX) && !defined(OS_IOS) + if (handle_vector_) { + for (auto it = handle_vector_->begin(); it != handle_vector_->end(); ) { + if (it->type == PlatformHandle::Type::MACH || + it->type == PlatformHandle::Type::MACH_NAME) { + // For Mach port names, we can can just leak them. They're not real + // ports anyways. For real ports, they're leaked because this is a child + // process and the remote process will take ownership. + it = handle_vector_->erase(it); + } else { + ++it; + } + } + } + return std::move(handle_vector_); +#else + return std::move(handle_vector_); +#endif +} + +#if defined(OS_WIN) +// static +bool Channel::Message::RewriteHandles(base::ProcessHandle from_process, + base::ProcessHandle to_process, + PlatformHandleVector* handles) { + bool success = true; + for (size_t i = 0; i < handles->size(); ++i) { + if (!(*handles)[i].is_valid()) { + DLOG(ERROR) << "Refusing to duplicate invalid handle."; + continue; + } + DCHECK_EQ((*handles)[i].owning_process, from_process); + BOOL result = DuplicateHandle( + from_process, (*handles)[i].handle, to_process, + &(*handles)[i].handle, 0, FALSE, + DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); + if (result) { + (*handles)[i].owning_process = to_process; + } else { + success = false; + + // If handle duplication fails, the source handle will already be closed + // due to DUPLICATE_CLOSE_SOURCE. Replace the handle in the message with + // an invalid handle. + (*handles)[i].handle = INVALID_HANDLE_VALUE; + (*handles)[i].owning_process = base::GetCurrentProcessHandle(); + } + } + return success; +} +#endif + +// Helper class for managing a Channel's read buffer allocations. This maintains +// a single contiguous buffer with the layout: +// +// [discarded bytes][occupied bytes][unoccupied bytes] +// +// The Reserve() method ensures that a certain capacity of unoccupied bytes are +// available. It does not claim that capacity and only allocates new capacity +// when strictly necessary. +// +// Claim() marks unoccupied bytes as occupied. +// +// Discard() marks occupied bytes as discarded, signifying that their contents +// can be forgotten or overwritten. +// +// Realign() moves occupied bytes to the front of the buffer so that those +// occupied bytes are properly aligned. +// +// The most common Channel behavior in practice should result in very few +// allocations and copies, as memory is claimed and discarded shortly after +// being reserved, and future reservations will immediately reuse discarded +// memory. +class Channel::ReadBuffer { + public: + ReadBuffer() { + size_ = kReadBufferSize; + data_ = static_cast(base::AlignedAlloc(size_, + kChannelMessageAlignment)); + } + + ~ReadBuffer() { + DCHECK(data_); + base::AlignedFree(data_); + } + + const char* occupied_bytes() const { return data_ + num_discarded_bytes_; } + + size_t num_occupied_bytes() const { + return num_occupied_bytes_ - num_discarded_bytes_; + } + + // Ensures the ReadBuffer has enough contiguous space allocated to hold + // |num_bytes| more bytes; returns the address of the first available byte. + char* Reserve(size_t num_bytes) { + if (num_occupied_bytes_ + num_bytes > size_) { + size_ = std::max(size_ * 2, num_occupied_bytes_ + num_bytes); + void* new_data = base::AlignedAlloc(size_, kChannelMessageAlignment); + memcpy(new_data, data_, num_occupied_bytes_); + base::AlignedFree(data_); + data_ = static_cast(new_data); + } + + return data_ + num_occupied_bytes_; + } + + // Marks the first |num_bytes| unoccupied bytes as occupied. + void Claim(size_t num_bytes) { + DCHECK_LE(num_occupied_bytes_ + num_bytes, size_); + num_occupied_bytes_ += num_bytes; + } + + // Marks the first |num_bytes| occupied bytes as discarded. This may result in + // shrinkage of the internal buffer, and it is not safe to assume the result + // of a previous Reserve() call is still valid after this. + void Discard(size_t num_bytes) { + DCHECK_LE(num_discarded_bytes_ + num_bytes, num_occupied_bytes_); + num_discarded_bytes_ += num_bytes; + + if (num_discarded_bytes_ == num_occupied_bytes_) { + // We can just reuse the buffer from the beginning in this common case. + num_discarded_bytes_ = 0; + num_occupied_bytes_ = 0; + } + + if (num_discarded_bytes_ > kMaxUnusedReadBufferCapacity) { + // In the uncommon case that we have a lot of discarded data at the + // front of the buffer, simply move remaining data to a smaller buffer. + size_t num_preserved_bytes = num_occupied_bytes_ - num_discarded_bytes_; + size_ = std::max(num_preserved_bytes, kReadBufferSize); + char* new_data = static_cast( + base::AlignedAlloc(size_, kChannelMessageAlignment)); + memcpy(new_data, data_ + num_discarded_bytes_, num_preserved_bytes); + base::AlignedFree(data_); + data_ = new_data; + num_discarded_bytes_ = 0; + num_occupied_bytes_ = num_preserved_bytes; + } + + if (num_occupied_bytes_ == 0 && size_ > kMaxUnusedReadBufferCapacity) { + // Opportunistically shrink the read buffer back down to a small size if + // it's grown very large. We only do this if there are no remaining + // unconsumed bytes in the buffer to avoid copies in most the common + // cases. + size_ = kMaxUnusedReadBufferCapacity; + base::AlignedFree(data_); + data_ = static_cast( + base::AlignedAlloc(size_, kChannelMessageAlignment)); + } + } + + void Realign() { + size_t num_bytes = num_occupied_bytes(); + memmove(data_, occupied_bytes(), num_bytes); + num_discarded_bytes_ = 0; + num_occupied_bytes_ = num_bytes; + } + + private: + char* data_ = nullptr; + + // The total size of the allocated buffer. + size_t size_ = 0; + + // The number of discarded bytes at the beginning of the allocated buffer. + size_t num_discarded_bytes_ = 0; + + // The total number of occupied bytes, including discarded bytes. + size_t num_occupied_bytes_ = 0; + + DISALLOW_COPY_AND_ASSIGN(ReadBuffer); +}; + +Channel::Channel(Delegate* delegate) + : delegate_(delegate), read_buffer_(new ReadBuffer) { +} + +Channel::~Channel() { +} + +void Channel::ShutDown() { + delegate_ = nullptr; + ShutDownImpl(); +} + +char* Channel::GetReadBuffer(size_t *buffer_capacity) { + DCHECK(read_buffer_); + size_t required_capacity = *buffer_capacity; + if (!required_capacity) + required_capacity = kReadBufferSize; + + *buffer_capacity = required_capacity; + return read_buffer_->Reserve(required_capacity); +} + +bool Channel::OnReadComplete(size_t bytes_read, size_t *next_read_size_hint) { + bool did_dispatch_message = false; + read_buffer_->Claim(bytes_read); + while (read_buffer_->num_occupied_bytes() >= sizeof(Message::LegacyHeader)) { + // Ensure the occupied data is properly aligned. If it isn't, a SIGBUS could + // happen on architectures that don't allow misaligned words access (i.e. + // anything other than x86). Only re-align when necessary to avoid copies. + if (!IsAlignedForChannelMessage( + reinterpret_cast(read_buffer_->occupied_bytes()))) { + read_buffer_->Realign(); + } + + // We have at least enough data available for a LegacyHeader. + const Message::LegacyHeader* legacy_header = + reinterpret_cast( + read_buffer_->occupied_bytes()); + + if (legacy_header->num_bytes < sizeof(Message::LegacyHeader) || + legacy_header->num_bytes > kMaxChannelMessageSize) { + LOG(ERROR) << "Invalid message size: " << legacy_header->num_bytes; + return false; + } + + if (read_buffer_->num_occupied_bytes() < legacy_header->num_bytes) { + // Not enough data available to read the full message. Hint to the + // implementation that it should try reading the full size of the message. + *next_read_size_hint = + legacy_header->num_bytes - read_buffer_->num_occupied_bytes(); + return true; + } + + const Message::Header* header = nullptr; + if (legacy_header->message_type != Message::MessageType::NORMAL_LEGACY) { + header = reinterpret_cast(legacy_header); + } + + size_t extra_header_size = 0; + const void* extra_header = nullptr; + size_t payload_size = 0; + void* payload = nullptr; + if (header) { + if (header->num_header_bytes < sizeof(Message::Header) || + header->num_header_bytes > header->num_bytes) { + LOG(ERROR) << "Invalid message header size: " + << header->num_header_bytes; + return false; + } + extra_header_size = header->num_header_bytes - sizeof(Message::Header); + extra_header = extra_header_size ? header + 1 : nullptr; + payload_size = header->num_bytes - header->num_header_bytes; + payload = payload_size + ? reinterpret_cast( + const_cast(read_buffer_->occupied_bytes()) + + header->num_header_bytes) + : nullptr; + } else { + payload_size = legacy_header->num_bytes - sizeof(Message::LegacyHeader); + payload = payload_size + ? const_cast(&legacy_header[1]) + : nullptr; + } + + const uint16_t num_handles = + header ? header->num_handles : legacy_header->num_handles; + ScopedPlatformHandleVectorPtr handles; + if (num_handles > 0) { + if (!GetReadPlatformHandles(num_handles, extra_header, extra_header_size, + &handles)) { + return false; + } + + if (!handles) { + // Not enough handles available for this message. + break; + } + } + + // We've got a complete message! Dispatch it and try another. + if (legacy_header->message_type != Message::MessageType::NORMAL_LEGACY && + legacy_header->message_type != Message::MessageType::NORMAL) { + if (!OnControlMessage(legacy_header->message_type, payload, payload_size, + std::move(handles))) { + return false; + } + did_dispatch_message = true; + } else if (delegate_) { + delegate_->OnChannelMessage(payload, payload_size, std::move(handles)); + did_dispatch_message = true; + } + + read_buffer_->Discard(legacy_header->num_bytes); + } + + *next_read_size_hint = did_dispatch_message ? 0 : kReadBufferSize; + return true; +} + +void Channel::OnError() { + if (delegate_) + delegate_->OnChannelError(); +} + +bool Channel::OnControlMessage(Message::MessageType message_type, + const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) { + return false; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/channel.h b/mojo/edk/system/channel.h new file mode 100644 index 0000000000000000000000000000000000000000..33a510c6f031968f554cb30b0a72e75348bbafbd --- /dev/null +++ b/mojo/edk/system/channel.h @@ -0,0 +1,303 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_CHANNEL_H_ +#define MOJO_EDK_SYSTEM_CHANNEL_H_ + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/process/process_handle.h" +#include "base/task_runner.h" +#include "mojo/edk/embedder/connection_params.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" + +namespace mojo { +namespace edk { + +const size_t kChannelMessageAlignment = 8; + +constexpr bool IsAlignedForChannelMessage(size_t n) { + return n % kChannelMessageAlignment == 0; +} + +// Channel provides a thread-safe interface to read and write arbitrary +// delimited messages over an underlying I/O channel, optionally transferring +// one or more platform handles in the process. +class MOJO_SYSTEM_IMPL_EXPORT Channel + : public base::RefCountedThreadSafe { + public: + struct Message; + + using MessagePtr = std::unique_ptr; + + // A message to be written to a channel. + struct MOJO_SYSTEM_IMPL_EXPORT Message { + enum class MessageType : uint16_t { + // An old format normal message, that uses the LegacyHeader. + // Only used on Android and ChromeOS. + // TODO(jcivelli): remove legacy support when Arc++ has updated to Mojo + // with normal versioned messages. crbug.com/695645 + NORMAL_LEGACY = 0, +#if defined(OS_MACOSX) + // A control message containing handles to echo back. + HANDLES_SENT, + // A control message containing handles that can now be closed. + HANDLES_SENT_ACK, +#endif + // A normal message that uses Header and can contain extra header values. + NORMAL, + }; + +#pragma pack(push, 1) + // Old message wire format for ChromeOS and Android, used by NORMAL_LEGACY + // messages. + struct LegacyHeader { + // Message size in bytes, including the header. + uint32_t num_bytes; + + // Number of attached handles. + uint16_t num_handles; + + MessageType message_type; + }; + + // Header used by NORMAL messages. + // To preserve backward compatibility with LegacyHeader, the num_bytes and + // message_type field must be at the same offset as in LegacyHeader. + struct Header { + // Message size in bytes, including the header. + uint32_t num_bytes; + + // Total size of header, including extra header data (i.e. HANDLEs on + // windows). + uint16_t num_header_bytes; + + MessageType message_type; + + // Number of attached handles. May be less than the reserved handle + // storage size in this message on platforms that serialise handles as + // data (i.e. HANDLEs on Windows, Mach ports on OSX). + uint16_t num_handles; + + char padding[6]; + }; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + struct MachPortsEntry { + // Index of Mach port in the original vector of PlatformHandles. + uint16_t index; + + // Mach port name. + uint32_t mach_port; + static_assert(sizeof(mach_port_t) <= sizeof(uint32_t), + "mach_port_t must be no larger than uint32_t"); + }; + static_assert(sizeof(MachPortsEntry) == 6, + "sizeof(MachPortsEntry) must be 6 bytes"); + + // Structure of the extra header field when present on OSX. + struct MachPortsExtraHeader { + // Actual number of Mach ports encoded in the extra header. + uint16_t num_ports; + + // Array of encoded Mach ports. If |num_ports| > 0, |entries[0]| through + // to |entries[num_ports-1]| inclusive are valid. + MachPortsEntry entries[0]; + }; + static_assert(sizeof(MachPortsExtraHeader) == 2, + "sizeof(MachPortsExtraHeader) must be 2 bytes"); +#elif defined(OS_WIN) + struct HandleEntry { + // The windows HANDLE. HANDLEs are guaranteed to fit inside 32-bits. + // See: https://msdn.microsoft.com/en-us/library/aa384203(VS.85).aspx + uint32_t handle; + }; + static_assert(sizeof(HandleEntry) == 4, + "sizeof(HandleEntry) must be 4 bytes"); +#endif +#pragma pack(pop) + + // Allocates and owns a buffer for message data with enough capacity for + // |payload_size| bytes plus a header, plus |max_handles| platform handles. + Message(size_t payload_size, size_t max_handles); + Message(size_t payload_size, size_t max_handles, MessageType message_type); + ~Message(); + + // Constructs a Message from serialized message data. + static MessagePtr Deserialize(const void* data, size_t data_num_bytes); + + const void* data() const { return data_; } + size_t data_num_bytes() const { return size_; } + + const void* extra_header() const; + void* mutable_extra_header(); + size_t extra_header_size() const; + + void* mutable_payload(); + const void* payload() const; + size_t payload_size() const; + + size_t num_handles() const; + bool has_handles() const; +#if defined(OS_MACOSX) && !defined(OS_IOS) + bool has_mach_ports() const; +#endif + + bool is_legacy_message() const; + LegacyHeader* legacy_header() const; + Header* header() const; + + // Note: SetHandles() and TakeHandles() invalidate any previous value of + // handles(). + void SetHandles(ScopedPlatformHandleVectorPtr new_handles); + ScopedPlatformHandleVectorPtr TakeHandles(); + // Version of TakeHandles that returns a vector of platform handles suitable + // for transfer over an underlying OS mechanism. i.e. file descriptors over + // a unix domain socket. Any handle that cannot be transferred this way, + // such as Mach ports, will be removed. + ScopedPlatformHandleVectorPtr TakeHandlesForTransport(); + +#if defined(OS_WIN) + // Prepares the handles in this message for use in a different process. + // Upon calling this the handles should belong to |from_process|; after the + // call they'll belong to |to_process|. The source handles are always + // closed by this call. Returns false iff one or more handles failed + // duplication. + static bool RewriteHandles(base::ProcessHandle from_process, + base::ProcessHandle to_process, + PlatformHandleVector* handles); +#endif + + void SetVersionForTest(uint16_t version_number); + + private: + size_t size_ = 0; + size_t max_handles_ = 0; + char* data_ = nullptr; + + ScopedPlatformHandleVectorPtr handle_vector_; + +#if defined(OS_WIN) + // On Windows, handles are serialised into the extra header section. + HandleEntry* handles_ = nullptr; +#elif defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, handles are serialised into the extra header section. + MachPortsExtraHeader* mach_ports_header_ = nullptr; +#endif + + DISALLOW_COPY_AND_ASSIGN(Message); + }; + + // Delegate methods are called from the I/O task runner with which the Channel + // was created (see Channel::Create). + class Delegate { + public: + virtual ~Delegate() {} + + // Notify of a received message. |payload| is not owned and must not be + // retained; it will be null if |payload_size| is 0. |handles| are + // transferred to the callee. + virtual void OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) = 0; + + // Notify that an error has occured and the Channel will cease operation. + virtual void OnChannelError() = 0; + }; + + // Creates a new Channel around a |platform_handle|, taking ownership of the + // handle. All I/O on the handle will be performed on |io_task_runner|. + // Note that ShutDown() MUST be called on the Channel some time before + // |delegate| is destroyed. + static scoped_refptr Create( + Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner); + + // Request that the channel be shut down. This should always be called before + // releasing the last reference to a Channel to ensure that it's cleaned up + // on its I/O task runner's thread. + // + // Delegate methods will no longer be invoked after this call. + void ShutDown(); + + // Begin processing I/O events. Delegate methods must only be invoked after + // this call. + virtual void Start() = 0; + + // Stop processing I/O events. + virtual void ShutDownImpl() = 0; + + // Queues an outgoing message on the Channel. This message will either + // eventually be written or will fail to write and trigger + // Delegate::OnChannelError. + virtual void Write(MessagePtr message) = 0; + + // Causes the platform handle to leak when this channel is shut down instead + // of closing it. + virtual void LeakHandle() = 0; + + protected: + explicit Channel(Delegate* delegate); + virtual ~Channel(); + + // Called by the implementation when it wants somewhere to stick data. + // |*buffer_capacity| may be set by the caller to indicate the desired buffer + // size. If 0, a sane default size will be used instead. + // + // Returns the address of a buffer which can be written to, and indicates its + // actual capacity in |*buffer_capacity|. + char* GetReadBuffer(size_t* buffer_capacity); + + // Called by the implementation when new data is available in the read + // buffer. Returns false to indicate an error. Upon success, + // |*next_read_size_hint| will be set to a recommended size for the next + // read done by the implementation. + bool OnReadComplete(size_t bytes_read, size_t* next_read_size_hint); + + // Called by the implementation when something goes horribly wrong. It is NOT + // OK to call this synchronously from any public interface methods. + void OnError(); + + // Retrieves the set of platform handles read for a given message. + // |extra_header| and |extra_header_size| correspond to the extra header data. + // Depending on the Channel implementation, this body may encode platform + // handles, or handles may be stored and managed elsewhere by the + // implementation. + // + // Returns |false| on unrecoverable error (i.e. the Channel should be closed). + // Returns |true| otherwise. Note that it is possible on some platforms for an + // insufficient number of handles to be available when this call is made, but + // this is not necessarily an error condition. In such cases this returns + // |true| but |*handles| will also be reset to null. + virtual bool GetReadPlatformHandles( + size_t num_handles, + const void* extra_header, + size_t extra_header_size, + ScopedPlatformHandleVectorPtr* handles) = 0; + + // Handles a received control message. Returns |true| if the message is + // accepted, or |false| otherwise. + virtual bool OnControlMessage(Message::MessageType message_type, + const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles); + + private: + friend class base::RefCountedThreadSafe; + + class ReadBuffer; + + Delegate* delegate_; + const std::unique_ptr read_buffer_; + + DISALLOW_COPY_AND_ASSIGN(Channel); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_CHANNEL_H_ diff --git a/mojo/edk/system/channel_posix.cc b/mojo/edk/system/channel_posix.cc new file mode 100644 index 0000000000000000000000000000000000000000..8b4ca7fdf3a754e32c0ac8d9096f82bc6257289b --- /dev/null +++ b/mojo/edk/system/channel_posix.cc @@ -0,0 +1,572 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/channel.h" + +#include +#include + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "mojo/edk/embedder/platform_channel_utils_posix.h" +#include "mojo/edk/embedder/platform_handle_vector.h" + +#if !defined(OS_NACL) +#include +#endif + +namespace mojo { +namespace edk { + +namespace { + +const size_t kMaxBatchReadCapacity = 256 * 1024; + +// A view over a Channel::Message object. The write queue uses these since +// large messages may need to be sent in chunks. +class MessageView { + public: + // Owns |message|. |offset| indexes the first unsent byte in the message. + MessageView(Channel::MessagePtr message, size_t offset) + : message_(std::move(message)), + offset_(offset), + handles_(message_->TakeHandlesForTransport()) { + DCHECK_GT(message_->data_num_bytes(), offset_); + } + + MessageView(MessageView&& other) { *this = std::move(other); } + + MessageView& operator=(MessageView&& other) { + message_ = std::move(other.message_); + offset_ = other.offset_; + handles_ = std::move(other.handles_); + return *this; + } + + ~MessageView() {} + + const void* data() const { + return static_cast(message_->data()) + offset_; + } + + size_t data_num_bytes() const { return message_->data_num_bytes() - offset_; } + + size_t data_offset() const { return offset_; } + void advance_data_offset(size_t num_bytes) { + DCHECK_GT(message_->data_num_bytes(), offset_ + num_bytes); + offset_ += num_bytes; + } + + ScopedPlatformHandleVectorPtr TakeHandles() { return std::move(handles_); } + Channel::MessagePtr TakeMessage() { return std::move(message_); } + + void SetHandles(ScopedPlatformHandleVectorPtr handles) { + handles_ = std::move(handles); + } + + private: + Channel::MessagePtr message_; + size_t offset_; + ScopedPlatformHandleVectorPtr handles_; + + DISALLOW_COPY_AND_ASSIGN(MessageView); +}; + +class ChannelPosix : public Channel, + public base::MessageLoop::DestructionObserver, + public base::MessageLoopForIO::Watcher { + public: + ChannelPosix(Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner) + : Channel(delegate), + self_(this), + handle_(connection_params.TakeChannelHandle()), + io_task_runner_(io_task_runner) +#if defined(OS_MACOSX) + , + handles_to_close_(new PlatformHandleVector) +#endif + { + CHECK(handle_.is_valid()); + } + + void Start() override { + if (io_task_runner_->RunsTasksOnCurrentThread()) { + StartOnIOThread(); + } else { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&ChannelPosix::StartOnIOThread, this)); + } + } + + void ShutDownImpl() override { + // Always shut down asynchronously when called through the public interface. + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&ChannelPosix::ShutDownOnIOThread, this)); + } + + void Write(MessagePtr message) override { + bool write_error = false; + { + base::AutoLock lock(write_lock_); + if (reject_writes_) + return; + if (outgoing_messages_.empty()) { + if (!WriteNoLock(MessageView(std::move(message), 0))) + reject_writes_ = write_error = true; + } else { + outgoing_messages_.emplace_back(std::move(message), 0); + } + } + if (write_error) { + // Do not synchronously invoke OnError(). Write() may have been called by + // the delegate and we don't want to re-enter it. + io_task_runner_->PostTask(FROM_HERE, + base::Bind(&ChannelPosix::OnError, this)); + } + } + + void LeakHandle() override { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + leak_handle_ = true; + } + + bool GetReadPlatformHandles( + size_t num_handles, + const void* extra_header, + size_t extra_header_size, + ScopedPlatformHandleVectorPtr* handles) override { + if (num_handles > std::numeric_limits::max()) + return false; +#if defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, we can have mach ports which are located in the extra header + // section. + using MachPortsEntry = Channel::Message::MachPortsEntry; + using MachPortsExtraHeader = Channel::Message::MachPortsExtraHeader; + CHECK(extra_header_size >= + sizeof(MachPortsExtraHeader) + num_handles * sizeof(MachPortsEntry)); + const MachPortsExtraHeader* mach_ports_header = + reinterpret_cast(extra_header); + size_t num_mach_ports = mach_ports_header->num_ports; + CHECK(num_mach_ports <= num_handles); + if (incoming_platform_handles_.size() + num_mach_ports < num_handles) { + handles->reset(); + return true; + } + + handles->reset(new PlatformHandleVector(num_handles)); + const MachPortsEntry* mach_ports = mach_ports_header->entries; + for (size_t i = 0, mach_port_index = 0; i < num_handles; ++i) { + if (mach_port_index < num_mach_ports && + mach_ports[mach_port_index].index == i) { + (*handles)->at(i) = PlatformHandle( + static_cast(mach_ports[mach_port_index].mach_port)); + CHECK((*handles)->at(i).type == PlatformHandle::Type::MACH); + // These are actually just Mach port names until they're resolved from + // the remote process. + (*handles)->at(i).type = PlatformHandle::Type::MACH_NAME; + mach_port_index++; + } else { + CHECK(!incoming_platform_handles_.empty()); + (*handles)->at(i) = incoming_platform_handles_.front(); + incoming_platform_handles_.pop_front(); + } + } +#else + if (incoming_platform_handles_.size() < num_handles) { + handles->reset(); + return true; + } + + handles->reset(new PlatformHandleVector(num_handles)); + for (size_t i = 0; i < num_handles; ++i) { + (*handles)->at(i) = incoming_platform_handles_.front(); + incoming_platform_handles_.pop_front(); + } +#endif + + return true; + } + + private: + ~ChannelPosix() override { + DCHECK(!read_watcher_); + DCHECK(!write_watcher_); + for (auto handle : incoming_platform_handles_) + handle.CloseIfNecessary(); + } + + void StartOnIOThread() { + DCHECK(!read_watcher_); + DCHECK(!write_watcher_); + read_watcher_.reset( + new base::MessageLoopForIO::FileDescriptorWatcher(FROM_HERE)); + base::MessageLoop::current()->AddDestructionObserver(this); + if (handle_.get().needs_connection) { + base::MessageLoopForIO::current()->WatchFileDescriptor( + handle_.get().handle, false /* persistent */, + base::MessageLoopForIO::WATCH_READ, read_watcher_.get(), this); + } else { + write_watcher_.reset( + new base::MessageLoopForIO::FileDescriptorWatcher(FROM_HERE)); + base::MessageLoopForIO::current()->WatchFileDescriptor( + handle_.get().handle, true /* persistent */, + base::MessageLoopForIO::WATCH_READ, read_watcher_.get(), this); + base::AutoLock lock(write_lock_); + FlushOutgoingMessagesNoLock(); + } + } + + void WaitForWriteOnIOThread() { + base::AutoLock lock(write_lock_); + WaitForWriteOnIOThreadNoLock(); + } + + void WaitForWriteOnIOThreadNoLock() { + if (pending_write_) + return; + if (!write_watcher_) + return; + if (io_task_runner_->RunsTasksOnCurrentThread()) { + pending_write_ = true; + base::MessageLoopForIO::current()->WatchFileDescriptor( + handle_.get().handle, false /* persistent */, + base::MessageLoopForIO::WATCH_WRITE, write_watcher_.get(), this); + } else { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&ChannelPosix::WaitForWriteOnIOThread, this)); + } + } + + void ShutDownOnIOThread() { + base::MessageLoop::current()->RemoveDestructionObserver(this); + + read_watcher_.reset(); + write_watcher_.reset(); + if (leak_handle_) + ignore_result(handle_.release()); + handle_.reset(); +#if defined(OS_MACOSX) + handles_to_close_.reset(); +#endif + + // May destroy the |this| if it was the last reference. + self_ = nullptr; + } + + // base::MessageLoop::DestructionObserver: + void WillDestroyCurrentMessageLoop() override { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + if (self_) + ShutDownOnIOThread(); + } + + // base::MessageLoopForIO::Watcher: + void OnFileCanReadWithoutBlocking(int fd) override { + CHECK_EQ(fd, handle_.get().handle); + if (handle_.get().needs_connection) { +#if !defined(OS_NACL) + read_watcher_.reset(); + base::MessageLoop::current()->RemoveDestructionObserver(this); + + ScopedPlatformHandle accept_fd; + ServerAcceptConnection(handle_.get(), &accept_fd); + if (!accept_fd.is_valid()) { + OnError(); + return; + } + handle_ = std::move(accept_fd); + StartOnIOThread(); +#else + NOTREACHED(); +#endif + return; + } + + bool read_error = false; + size_t next_read_size = 0; + size_t buffer_capacity = 0; + size_t total_bytes_read = 0; + size_t bytes_read = 0; + do { + buffer_capacity = next_read_size; + char* buffer = GetReadBuffer(&buffer_capacity); + DCHECK_GT(buffer_capacity, 0u); + + ssize_t read_result = PlatformChannelRecvmsg( + handle_.get(), + buffer, + buffer_capacity, + &incoming_platform_handles_); + + if (read_result > 0) { + bytes_read = static_cast(read_result); + total_bytes_read += bytes_read; + if (!OnReadComplete(bytes_read, &next_read_size)) { + read_error = true; + break; + } + } else if (read_result == 0 || + (errno != EAGAIN && errno != EWOULDBLOCK)) { + read_error = true; + break; + } + } while (bytes_read == buffer_capacity && + total_bytes_read < kMaxBatchReadCapacity && + next_read_size > 0); + if (read_error) { + // Stop receiving read notifications. + read_watcher_.reset(); + + OnError(); + } + } + + void OnFileCanWriteWithoutBlocking(int fd) override { + bool write_error = false; + { + base::AutoLock lock(write_lock_); + pending_write_ = false; + if (!FlushOutgoingMessagesNoLock()) + reject_writes_ = write_error = true; + } + if (write_error) + OnError(); + } + + // Attempts to write a message directly to the channel. If the full message + // cannot be written, it's queued and a wait is initiated to write the message + // ASAP on the I/O thread. + bool WriteNoLock(MessageView message_view) { + if (handle_.get().needs_connection) { + outgoing_messages_.emplace_front(std::move(message_view)); + return true; + } + size_t bytes_written = 0; + do { + message_view.advance_data_offset(bytes_written); + + ssize_t result; + ScopedPlatformHandleVectorPtr handles = message_view.TakeHandles(); + if (handles && handles->size()) { + iovec iov = { + const_cast(message_view.data()), + message_view.data_num_bytes() + }; + // TODO: Handle lots of handles. + result = PlatformChannelSendmsgWithHandles( + handle_.get(), &iov, 1, handles->data(), handles->size()); + if (result >= 0) { +#if defined(OS_MACOSX) + // There is a bug on OSX which makes it dangerous to close + // a file descriptor while it is in transit. So instead we + // store the file descriptor in a set and send a message to + // the recipient, which is queued AFTER the message that + // sent the FD. The recipient will reply to the message, + // letting us know that it is now safe to close the file + // descriptor. For more information, see: + // http://crbug.com/298276 + std::vector fds; + for (auto& handle : *handles) + fds.push_back(handle.handle); + { + base::AutoLock l(handles_to_close_lock_); + for (auto& handle : *handles) + handles_to_close_->push_back(handle); + } + MessagePtr fds_message( + new Channel::Message(sizeof(fds[0]) * fds.size(), 0, + Message::MessageType::HANDLES_SENT)); + memcpy(fds_message->mutable_payload(), fds.data(), + sizeof(fds[0]) * fds.size()); + outgoing_messages_.emplace_back(std::move(fds_message), 0); + handles->clear(); +#else + handles.reset(); +#endif // defined(OS_MACOSX) + } + } else { + result = PlatformChannelWrite(handle_.get(), message_view.data(), + message_view.data_num_bytes()); + } + + if (result < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK +#if defined(OS_MACOSX) + // On OS X if sendmsg() is trying to send fds between processes and + // there isn't enough room in the output buffer to send the fd + // structure over atomically then EMSGSIZE is returned. + // + // EMSGSIZE presents a problem since the system APIs can only call + // us when there's room in the socket buffer and not when there is + // "enough" room. + // + // The current behavior is to return to the event loop when EMSGSIZE + // is received and hopefull service another FD. This is however + // still technically a busy wait since the event loop will call us + // right back until the receiver has read enough data to allow + // passing the FD over atomically. + && errno != EMSGSIZE +#endif + ) { + return false; + } + message_view.SetHandles(std::move(handles)); + outgoing_messages_.emplace_front(std::move(message_view)); + WaitForWriteOnIOThreadNoLock(); + return true; + } + + bytes_written = static_cast(result); + } while (bytes_written < message_view.data_num_bytes()); + + return FlushOutgoingMessagesNoLock(); + } + + bool FlushOutgoingMessagesNoLock() { + std::deque messages; + std::swap(outgoing_messages_, messages); + + while (!messages.empty()) { + if (!WriteNoLock(std::move(messages.front()))) + return false; + + messages.pop_front(); + if (!outgoing_messages_.empty()) { + // The message was requeued by WriteNoLock(), so we have to wait for + // pipe to become writable again. Repopulate the message queue and exit. + // If sending the message triggered any control messages, they may be + // in |outgoing_messages_| in addition to or instead of the message + // being sent. + std::swap(messages, outgoing_messages_); + while (!messages.empty()) { + outgoing_messages_.push_front(std::move(messages.back())); + messages.pop_back(); + } + return true; + } + } + + return true; + } + +#if defined(OS_MACOSX) + bool OnControlMessage(Message::MessageType message_type, + const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) override { + switch (message_type) { + case Message::MessageType::HANDLES_SENT: { + if (payload_size == 0) + break; + MessagePtr message(new Channel::Message( + payload_size, 0, Message::MessageType::HANDLES_SENT_ACK)); + memcpy(message->mutable_payload(), payload, payload_size); + Write(std::move(message)); + return true; + } + + case Message::MessageType::HANDLES_SENT_ACK: { + size_t num_fds = payload_size / sizeof(int); + if (num_fds == 0 || payload_size % sizeof(int) != 0) + break; + + const int* fds = reinterpret_cast(payload); + if (!CloseHandles(fds, num_fds)) + break; + return true; + } + + default: + break; + } + + return false; + } + + // Closes handles referenced by |fds|. Returns false if |num_fds| is 0, or if + // |fds| does not match a sequence of handles in |handles_to_close_|. + bool CloseHandles(const int* fds, size_t num_fds) { + base::AutoLock l(handles_to_close_lock_); + if (!num_fds) + return false; + + auto start = + std::find_if(handles_to_close_->begin(), handles_to_close_->end(), + [&fds](const PlatformHandle& handle) { + return handle.handle == fds[0]; + }); + if (start == handles_to_close_->end()) + return false; + + auto it = start; + size_t i = 0; + // The FDs in the message should match a sequence of handles in + // |handles_to_close_|. + for (; i < num_fds && it != handles_to_close_->end(); i++, ++it) { + if (it->handle != fds[i]) + return false; + + it->CloseIfNecessary(); + } + if (i != num_fds) + return false; + + handles_to_close_->erase(start, it); + return true; + } +#endif // defined(OS_MACOSX) + + // Keeps the Channel alive at least until explicit shutdown on the IO thread. + scoped_refptr self_; + + ScopedPlatformHandle handle_; + scoped_refptr io_task_runner_; + + // These watchers must only be accessed on the IO thread. + std::unique_ptr read_watcher_; + std::unique_ptr write_watcher_; + + std::deque incoming_platform_handles_; + + // Protects |pending_write_| and |outgoing_messages_|. + base::Lock write_lock_; + bool pending_write_ = false; + bool reject_writes_ = false; + std::deque outgoing_messages_; + + bool leak_handle_ = false; + +#if defined(OS_MACOSX) + base::Lock handles_to_close_lock_; + ScopedPlatformHandleVectorPtr handles_to_close_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ChannelPosix); +}; + +} // namespace + +// static +scoped_refptr Channel::Create( + Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner) { + return new ChannelPosix(delegate, std::move(connection_params), + io_task_runner); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/channel_unittest.cc b/mojo/edk/system/channel_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..ce2c804d55f4bf289a67535c54229fa35d2bed49 --- /dev/null +++ b/mojo/edk/system/channel_unittest.cc @@ -0,0 +1,177 @@ +// 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. + +#include "mojo/edk/system/channel.h" +#include "base/memory/ptr_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +class TestChannel : public Channel { + public: + TestChannel(Channel::Delegate* delegate) : Channel(delegate) {} + + char* GetReadBufferTest(size_t* buffer_capacity) { + return GetReadBuffer(buffer_capacity); + } + + bool OnReadCompleteTest(size_t bytes_read, size_t* next_read_size_hint) { + return OnReadComplete(bytes_read, next_read_size_hint); + } + + MOCK_METHOD4(GetReadPlatformHandles, + bool(size_t num_handles, + const void* extra_header, + size_t extra_header_size, + ScopedPlatformHandleVectorPtr* handles)); + MOCK_METHOD0(Start, void()); + MOCK_METHOD0(ShutDownImpl, void()); + MOCK_METHOD0(LeakHandle, void()); + + void Write(MessagePtr message) {} + + protected: + ~TestChannel() override {} +}; + +// Not using GMock as I don't think it supports movable types. +class MockChannelDelegate : public Channel::Delegate { + public: + MockChannelDelegate() {} + + size_t GetReceivedPayloadSize() const { return payload_size_; } + + const void* GetReceivedPayload() const { return payload_.get(); } + + protected: + void OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) override { + payload_.reset(new char[payload_size]); + memcpy(payload_.get(), payload, payload_size); + payload_size_ = payload_size; + } + + // Notify that an error has occured and the Channel will cease operation. + void OnChannelError() override {} + + private: + size_t payload_size_ = 0; + std::unique_ptr payload_; +}; + +Channel::MessagePtr CreateDefaultMessage(bool legacy_message) { + const size_t payload_size = 100; + Channel::MessagePtr message = base::MakeUnique( + payload_size, 0, + legacy_message ? Channel::Message::MessageType::NORMAL_LEGACY + : Channel::Message::MessageType::NORMAL); + char* payload = static_cast(message->mutable_payload()); + for (size_t i = 0; i < payload_size; i++) { + payload[i] = static_cast(i); + } + return message; +} + +void TestMemoryEqual(const void* data1, + size_t data1_size, + const void* data2, + size_t data2_size) { + ASSERT_EQ(data1_size, data2_size); + const unsigned char* data1_char = static_cast(data1); + const unsigned char* data2_char = static_cast(data2); + for (size_t i = 0; i < data1_size; i++) { + // ASSERT so we don't log tons of errors if the data is different. + ASSERT_EQ(data1_char[i], data2_char[i]); + } +} + +void TestMessagesAreEqual(Channel::Message* message1, + Channel::Message* message2, + bool legacy_messages) { + // If any of the message is null, this is probably not what you wanted to + // test. + ASSERT_NE(nullptr, message1); + ASSERT_NE(nullptr, message2); + + ASSERT_EQ(message1->payload_size(), message2->payload_size()); + EXPECT_EQ(message1->has_handles(), message2->has_handles()); + + TestMemoryEqual(message1->payload(), message1->payload_size(), + message2->payload(), message2->payload_size()); + + if (legacy_messages) + return; + + ASSERT_EQ(message1->extra_header_size(), message2->extra_header_size()); + TestMemoryEqual(message1->extra_header(), message1->extra_header_size(), + message2->extra_header(), message2->extra_header_size()); +} + +TEST(ChannelTest, LegacyMessageDeserialization) { + Channel::MessagePtr message = CreateDefaultMessage(true /* legacy_message */); + Channel::MessagePtr deserialized_message = + Channel::Message::Deserialize(message->data(), message->data_num_bytes()); + TestMessagesAreEqual(message.get(), deserialized_message.get(), + true /* legacy_message */); +} + +TEST(ChannelTest, NonLegacyMessageDeserialization) { + Channel::MessagePtr message = + CreateDefaultMessage(false /* legacy_message */); + Channel::MessagePtr deserialized_message = + Channel::Message::Deserialize(message->data(), message->data_num_bytes()); + TestMessagesAreEqual(message.get(), deserialized_message.get(), + false /* legacy_message */); +} + +TEST(ChannelTest, OnReadLegacyMessage) { + size_t buffer_size = 100 * 1024; + Channel::MessagePtr message = CreateDefaultMessage(true /* legacy_message */); + + MockChannelDelegate channel_delegate; + scoped_refptr channel = new TestChannel(&channel_delegate); + char* read_buffer = channel->GetReadBufferTest(&buffer_size); + ASSERT_LT(message->data_num_bytes(), + buffer_size); // Bad test. Increase buffer + // size. + memcpy(read_buffer, message->data(), message->data_num_bytes()); + + size_t next_read_size_hint = 0; + EXPECT_TRUE(channel->OnReadCompleteTest(message->data_num_bytes(), + &next_read_size_hint)); + + TestMemoryEqual(message->payload(), message->payload_size(), + channel_delegate.GetReceivedPayload(), + channel_delegate.GetReceivedPayloadSize()); +} + +TEST(ChannelTest, OnReadNonLegacyMessage) { + size_t buffer_size = 100 * 1024; + Channel::MessagePtr message = + CreateDefaultMessage(false /* legacy_message */); + + MockChannelDelegate channel_delegate; + scoped_refptr channel = new TestChannel(&channel_delegate); + char* read_buffer = channel->GetReadBufferTest(&buffer_size); + ASSERT_LT(message->data_num_bytes(), + buffer_size); // Bad test. Increase buffer + // size. + memcpy(read_buffer, message->data(), message->data_num_bytes()); + + size_t next_read_size_hint = 0; + EXPECT_TRUE(channel->OnReadCompleteTest(message->data_num_bytes(), + &next_read_size_hint)); + + TestMemoryEqual(message->payload(), message->payload_size(), + channel_delegate.GetReceivedPayload(), + channel_delegate.GetReceivedPayloadSize()); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/channel_win.cc b/mojo/edk/system/channel_win.cc new file mode 100644 index 0000000000000000000000000000000000000000..c15df16bb1341308c6db89de41ceea9cbb7fa16c --- /dev/null +++ b/mojo/edk/system/channel_win.cc @@ -0,0 +1,360 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/channel.h" + +#include +#include + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "base/win/win_util.h" +#include "mojo/edk/embedder/platform_handle_vector.h" + +namespace mojo { +namespace edk { + +namespace { + +// A view over a Channel::Message object. The write queue uses these since +// large messages may need to be sent in chunks. +class MessageView { + public: + // Owns |message|. |offset| indexes the first unsent byte in the message. + MessageView(Channel::MessagePtr message, size_t offset) + : message_(std::move(message)), + offset_(offset) { + DCHECK_GT(message_->data_num_bytes(), offset_); + } + + MessageView(MessageView&& other) { *this = std::move(other); } + + MessageView& operator=(MessageView&& other) { + message_ = std::move(other.message_); + offset_ = other.offset_; + return *this; + } + + ~MessageView() {} + + const void* data() const { + return static_cast(message_->data()) + offset_; + } + + size_t data_num_bytes() const { return message_->data_num_bytes() - offset_; } + + size_t data_offset() const { return offset_; } + void advance_data_offset(size_t num_bytes) { + DCHECK_GE(message_->data_num_bytes(), offset_ + num_bytes); + offset_ += num_bytes; + } + + Channel::MessagePtr TakeChannelMessage() { return std::move(message_); } + + private: + Channel::MessagePtr message_; + size_t offset_; + + DISALLOW_COPY_AND_ASSIGN(MessageView); +}; + +class ChannelWin : public Channel, + public base::MessageLoop::DestructionObserver, + public base::MessageLoopForIO::IOHandler { + public: + ChannelWin(Delegate* delegate, + ScopedPlatformHandle handle, + scoped_refptr io_task_runner) + : Channel(delegate), + self_(this), + handle_(std::move(handle)), + io_task_runner_(io_task_runner) { + CHECK(handle_.is_valid()); + + wait_for_connect_ = handle_.get().needs_connection; + } + + void Start() override { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&ChannelWin::StartOnIOThread, this)); + } + + void ShutDownImpl() override { + // Always shut down asynchronously when called through the public interface. + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&ChannelWin::ShutDownOnIOThread, this)); + } + + void Write(MessagePtr message) override { + bool write_error = false; + { + base::AutoLock lock(write_lock_); + if (reject_writes_) + return; + + bool write_now = !delay_writes_ && outgoing_messages_.empty(); + outgoing_messages_.emplace_back(std::move(message), 0); + + if (write_now && !WriteNoLock(outgoing_messages_.front())) + reject_writes_ = write_error = true; + } + if (write_error) { + // Do not synchronously invoke OnError(). Write() may have been called by + // the delegate and we don't want to re-enter it. + io_task_runner_->PostTask(FROM_HERE, + base::Bind(&ChannelWin::OnError, this)); + } + } + + void LeakHandle() override { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + leak_handle_ = true; + } + + bool GetReadPlatformHandles( + size_t num_handles, + const void* extra_header, + size_t extra_header_size, + ScopedPlatformHandleVectorPtr* handles) override { + if (num_handles > std::numeric_limits::max()) + return false; + using HandleEntry = Channel::Message::HandleEntry; + size_t handles_size = sizeof(HandleEntry) * num_handles; + if (handles_size > extra_header_size) + return false; + DCHECK(extra_header); + handles->reset(new PlatformHandleVector(num_handles)); + const HandleEntry* extra_header_handles = + reinterpret_cast(extra_header); + for (size_t i = 0; i < num_handles; i++) { + (*handles)->at(i).handle = + base::win::Uint32ToHandle(extra_header_handles[i].handle); + } + return true; + } + + private: + // May run on any thread. + ~ChannelWin() override {} + + void StartOnIOThread() { + base::MessageLoop::current()->AddDestructionObserver(this); + base::MessageLoopForIO::current()->RegisterIOHandler( + handle_.get().handle, this); + + if (wait_for_connect_) { + BOOL ok = ConnectNamedPipe(handle_.get().handle, + &connect_context_.overlapped); + if (ok) { + PLOG(ERROR) << "Unexpected success while waiting for pipe connection"; + OnError(); + return; + } + + const DWORD err = GetLastError(); + switch (err) { + case ERROR_PIPE_CONNECTED: + wait_for_connect_ = false; + break; + case ERROR_IO_PENDING: + AddRef(); + return; + case ERROR_NO_DATA: + OnError(); + return; + } + } + + // Now that we have registered our IOHandler, we can start writing. + { + base::AutoLock lock(write_lock_); + if (delay_writes_) { + delay_writes_ = false; + WriteNextNoLock(); + } + } + + // Keep this alive in case we synchronously run shutdown. + scoped_refptr keep_alive(this); + ReadMore(0); + } + + void ShutDownOnIOThread() { + base::MessageLoop::current()->RemoveDestructionObserver(this); + + // BUG(crbug.com/583525): This function is expected to be called once, and + // |handle_| should be valid at this point. + CHECK(handle_.is_valid()); + CancelIo(handle_.get().handle); + if (leak_handle_) + ignore_result(handle_.release()); + handle_.reset(); + + // May destroy the |this| if it was the last reference. + self_ = nullptr; + } + + // base::MessageLoop::DestructionObserver: + void WillDestroyCurrentMessageLoop() override { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + if (self_) + ShutDownOnIOThread(); + } + + // base::MessageLoop::IOHandler: + void OnIOCompleted(base::MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, + DWORD error) override { + if (error != ERROR_SUCCESS) { + OnError(); + } else if (context == &connect_context_) { + DCHECK(wait_for_connect_); + wait_for_connect_ = false; + ReadMore(0); + + base::AutoLock lock(write_lock_); + if (delay_writes_) { + delay_writes_ = false; + WriteNextNoLock(); + } + } else if (context == &read_context_) { + OnReadDone(static_cast(bytes_transfered)); + } else { + CHECK(context == &write_context_); + OnWriteDone(static_cast(bytes_transfered)); + } + Release(); // Balancing reference taken after ReadFile / WriteFile. + } + + void OnReadDone(size_t bytes_read) { + if (bytes_read > 0) { + size_t next_read_size = 0; + if (OnReadComplete(bytes_read, &next_read_size)) { + ReadMore(next_read_size); + } else { + OnError(); + } + } else if (bytes_read == 0) { + OnError(); + } + } + + void OnWriteDone(size_t bytes_written) { + if (bytes_written == 0) + return; + + bool write_error = false; + { + base::AutoLock lock(write_lock_); + + DCHECK(!outgoing_messages_.empty()); + + MessageView& message_view = outgoing_messages_.front(); + message_view.advance_data_offset(bytes_written); + if (message_view.data_num_bytes() == 0) { + Channel::MessagePtr message = message_view.TakeChannelMessage(); + outgoing_messages_.pop_front(); + + // Clear any handles so they don't get closed on destruction. + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + if (handles) + handles->clear(); + } + + if (!WriteNextNoLock()) + reject_writes_ = write_error = true; + } + if (write_error) + OnError(); + } + + void ReadMore(size_t next_read_size_hint) { + size_t buffer_capacity = next_read_size_hint; + char* buffer = GetReadBuffer(&buffer_capacity); + DCHECK_GT(buffer_capacity, 0u); + + BOOL ok = ReadFile(handle_.get().handle, + buffer, + static_cast(buffer_capacity), + NULL, + &read_context_.overlapped); + + if (ok || GetLastError() == ERROR_IO_PENDING) { + AddRef(); // Will be balanced in OnIOCompleted + } else { + OnError(); + } + } + + // Attempts to write a message directly to the channel. If the full message + // cannot be written, it's queued and a wait is initiated to write the message + // ASAP on the I/O thread. + bool WriteNoLock(const MessageView& message_view) { + BOOL ok = WriteFile(handle_.get().handle, + message_view.data(), + static_cast(message_view.data_num_bytes()), + NULL, + &write_context_.overlapped); + + if (ok || GetLastError() == ERROR_IO_PENDING) { + AddRef(); // Will be balanced in OnIOCompleted. + return true; + } + return false; + } + + bool WriteNextNoLock() { + if (outgoing_messages_.empty()) + return true; + return WriteNoLock(outgoing_messages_.front()); + } + + // Keeps the Channel alive at least until explicit shutdown on the IO thread. + scoped_refptr self_; + + ScopedPlatformHandle handle_; + scoped_refptr io_task_runner_; + + base::MessageLoopForIO::IOContext connect_context_; + base::MessageLoopForIO::IOContext read_context_; + base::MessageLoopForIO::IOContext write_context_; + + // Protects |reject_writes_| and |outgoing_messages_|. + base::Lock write_lock_; + + bool delay_writes_ = true; + + bool reject_writes_ = false; + std::deque outgoing_messages_; + + bool wait_for_connect_; + + bool leak_handle_ = false; + + DISALLOW_COPY_AND_ASSIGN(ChannelWin); +}; + +} // namespace + +// static +scoped_refptr Channel::Create( + Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner) { + return new ChannelWin(delegate, connection_params.TakeChannelHandle(), + io_task_runner); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/configuration.cc b/mojo/edk/system/configuration.cc new file mode 100644 index 0000000000000000000000000000000000000000..f5eb2b8f6fe8b1538d0d0febd49db8e1901df033 --- /dev/null +++ b/mojo/edk/system/configuration.cc @@ -0,0 +1,25 @@ +// Copyright 2014 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. + +#include "mojo/edk/system/configuration.h" + +namespace mojo { +namespace edk { +namespace internal { + +// These default values should be synced with the documentation in +// mojo/edk/embedder/configuration.h. +Configuration g_configuration = { + 1000000, // max_handle_table_size + 1000000, // max_mapping_table_sze + 4 * 1024 * 1024, // max_message_num_bytes + 10000, // max_message_num_handles + 256 * 1024 * 1024, // max_data_pipe_capacity_bytes + 1024 * 1024, // default_data_pipe_capacity_bytes + 16, // data_pipe_buffer_alignment_bytes + 1024 * 1024 * 1024}; // max_shared_memory_num_bytes + +} // namespace internal +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/configuration.h b/mojo/edk/system/configuration.h new file mode 100644 index 0000000000000000000000000000000000000000..038835ffdd9e8003581c6b17d4afb0772de6bb35 --- /dev/null +++ b/mojo/edk/system/configuration.h @@ -0,0 +1,29 @@ +// Copyright 2014 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 MOJO_EDK_SYSTEM_CONFIGURATION_H_ +#define MOJO_EDK_SYSTEM_CONFIGURATION_H_ + +#include "mojo/edk/embedder/configuration.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +namespace internal { +MOJO_SYSTEM_IMPL_EXPORT extern Configuration g_configuration; +} // namespace internal + +MOJO_SYSTEM_IMPL_EXPORT inline const Configuration& GetConfiguration() { + return internal::g_configuration; +} + +MOJO_SYSTEM_IMPL_EXPORT inline Configuration* GetMutableConfiguration() { + return &internal::g_configuration; +} + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_CONFIGURATION_H_ diff --git a/mojo/edk/system/core.cc b/mojo/edk/system/core.cc new file mode 100644 index 0000000000000000000000000000000000000000..360e8c3012fd56be5c239ca454a66a975b92c59e --- /dev/null +++ b/mojo/edk/system/core.cc @@ -0,0 +1,1019 @@ +// Copyright 2013 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. + +#include "mojo/edk/system/core.h" + +#include + +#include + +#include "base/bind.h" +#include "base/containers/stack_container.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/rand_util.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/channel.h" +#include "mojo/edk/system/configuration.h" +#include "mojo/edk/system/data_pipe_consumer_dispatcher.h" +#include "mojo/edk/system/data_pipe_producer_dispatcher.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/message_for_transit.h" +#include "mojo/edk/system/message_pipe_dispatcher.h" +#include "mojo/edk/system/platform_handle_dispatcher.h" +#include "mojo/edk/system/ports/name.h" +#include "mojo/edk/system/ports/node.h" +#include "mojo/edk/system/request_context.h" +#include "mojo/edk/system/shared_buffer_dispatcher.h" +#include "mojo/edk/system/watcher_dispatcher.h" + +namespace mojo { +namespace edk { + +namespace { + +// This is an unnecessarily large limit that is relatively easy to enforce. +const uint32_t kMaxHandlesPerMessage = 1024 * 1024; + +// TODO(rockot): Maybe we could negotiate a debugging pipe ID for cross-process +// pipes too; for now we just use a constant. This only affects bootstrap pipes. +const uint64_t kUnknownPipeIdForDebug = 0x7f7f7f7f7f7f7f7fUL; + +MojoResult MojoPlatformHandleToScopedPlatformHandle( + const MojoPlatformHandle* platform_handle, + ScopedPlatformHandle* out_handle) { + if (platform_handle->struct_size != sizeof(MojoPlatformHandle)) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (platform_handle->type == MOJO_PLATFORM_HANDLE_TYPE_INVALID) { + out_handle->reset(); + return MOJO_RESULT_OK; + } + + PlatformHandle handle; + switch (platform_handle->type) { +#if defined(OS_POSIX) + case MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR: + handle.handle = static_cast(platform_handle->value); + break; +#endif + +#if defined(OS_MACOSX) && !defined(OS_IOS) + case MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT: + handle.type = PlatformHandle::Type::MACH; + handle.port = static_cast(platform_handle->value); + break; +#endif + +#if defined(OS_WIN) + case MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE: + handle.handle = reinterpret_cast(platform_handle->value); + break; +#endif + + default: + return MOJO_RESULT_INVALID_ARGUMENT; + } + + out_handle->reset(handle); + return MOJO_RESULT_OK; +} + +MojoResult ScopedPlatformHandleToMojoPlatformHandle( + ScopedPlatformHandle handle, + MojoPlatformHandle* platform_handle) { + if (platform_handle->struct_size != sizeof(MojoPlatformHandle)) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (!handle.is_valid()) { + platform_handle->type = MOJO_PLATFORM_HANDLE_TYPE_INVALID; + return MOJO_RESULT_OK; + } + +#if defined(OS_POSIX) + switch (handle.get().type) { + case PlatformHandle::Type::POSIX: + platform_handle->type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR; + platform_handle->value = static_cast(handle.release().handle); + break; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + case PlatformHandle::Type::MACH: + platform_handle->type = MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT; + platform_handle->value = static_cast(handle.release().port); + break; +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + + default: + return MOJO_RESULT_INVALID_ARGUMENT; + } +#elif defined(OS_WIN) + platform_handle->type = MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE; + platform_handle->value = reinterpret_cast(handle.release().handle); +#endif // defined(OS_WIN) + + return MOJO_RESULT_OK; +} + +} // namespace + +Core::Core() {} + +Core::~Core() { + if (node_controller_ && node_controller_->io_task_runner()) { + // If this races with IO thread shutdown the callback will be dropped and + // the NodeController will be shutdown on this thread anyway, which is also + // just fine. + scoped_refptr io_task_runner = + node_controller_->io_task_runner(); + io_task_runner->PostTask(FROM_HERE, + base::Bind(&Core::PassNodeControllerToIOThread, + base::Passed(&node_controller_))); + } +} + +void Core::SetIOTaskRunner(scoped_refptr io_task_runner) { + GetNodeController()->SetIOTaskRunner(io_task_runner); +} + +NodeController* Core::GetNodeController() { + base::AutoLock lock(node_controller_lock_); + if (!node_controller_) + node_controller_.reset(new NodeController(this)); + return node_controller_.get(); +} + +scoped_refptr Core::GetDispatcher(MojoHandle handle) { + base::AutoLock lock(handles_lock_); + return handles_.GetDispatcher(handle); +} + +void Core::SetDefaultProcessErrorCallback( + const ProcessErrorCallback& callback) { + default_process_error_callback_ = callback; +} + +void Core::AddChild(base::ProcessHandle process_handle, + ConnectionParams connection_params, + const std::string& child_token, + const ProcessErrorCallback& process_error_callback) { + GetNodeController()->ConnectToChild(process_handle, + std::move(connection_params), child_token, + process_error_callback); +} + +void Core::ChildLaunchFailed(const std::string& child_token) { + RequestContext request_context; + GetNodeController()->CloseChildPorts(child_token); +} + +ScopedMessagePipeHandle Core::ConnectToPeerProcess( + ScopedPlatformHandle pipe_handle, + const std::string& peer_token) { + RequestContext request_context; + ports::PortRef port0, port1; + GetNodeController()->node()->CreatePortPair(&port0, &port1); + MojoHandle handle = AddDispatcher(new MessagePipeDispatcher( + GetNodeController(), port0, kUnknownPipeIdForDebug, 0)); + ConnectionParams connection_params(std::move(pipe_handle)); + GetNodeController()->ConnectToPeer(std::move(connection_params), port1, + peer_token); + return ScopedMessagePipeHandle(MessagePipeHandle(handle)); +} + +void Core::ClosePeerConnection(const std::string& peer_token) { + GetNodeController()->ClosePeerConnection(peer_token); +} + +void Core::InitChild(ConnectionParams connection_params) { + GetNodeController()->ConnectToParent(std::move(connection_params)); +} + +void Core::SetMachPortProvider(base::PortProvider* port_provider) { +#if defined(OS_MACOSX) && !defined(OS_IOS) + GetNodeController()->CreateMachPortRelay(port_provider); +#endif +} + +MojoHandle Core::AddDispatcher(scoped_refptr dispatcher) { + base::AutoLock lock(handles_lock_); + return handles_.AddDispatcher(dispatcher); +} + +bool Core::AddDispatchersFromTransit( + const std::vector& dispatchers, + MojoHandle* handles) { + bool failed = false; + { + base::AutoLock lock(handles_lock_); + if (!handles_.AddDispatchersFromTransit(dispatchers, handles)) + failed = true; + } + if (failed) { + for (auto d : dispatchers) + d.dispatcher->Close(); + return false; + } + return true; +} + +MojoResult Core::CreatePlatformHandleWrapper( + ScopedPlatformHandle platform_handle, + MojoHandle* wrapper_handle) { + MojoHandle h = AddDispatcher( + PlatformHandleDispatcher::Create(std::move(platform_handle))); + if (h == MOJO_HANDLE_INVALID) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + *wrapper_handle = h; + return MOJO_RESULT_OK; +} + +MojoResult Core::PassWrappedPlatformHandle( + MojoHandle wrapper_handle, + ScopedPlatformHandle* platform_handle) { + base::AutoLock lock(handles_lock_); + scoped_refptr d; + MojoResult result = handles_.GetAndRemoveDispatcher(wrapper_handle, &d); + if (result != MOJO_RESULT_OK) + return result; + if (d->GetType() == Dispatcher::Type::PLATFORM_HANDLE) { + PlatformHandleDispatcher* phd = + static_cast(d.get()); + *platform_handle = phd->PassPlatformHandle(); + } else { + result = MOJO_RESULT_INVALID_ARGUMENT; + } + d->Close(); + return result; +} + +MojoResult Core::CreateSharedBufferWrapper( + base::SharedMemoryHandle shared_memory_handle, + size_t num_bytes, + bool read_only, + MojoHandle* mojo_wrapper_handle) { + DCHECK(num_bytes); + scoped_refptr platform_buffer = + PlatformSharedBuffer::CreateFromSharedMemoryHandle(num_bytes, read_only, + shared_memory_handle); + if (!platform_buffer) + return MOJO_RESULT_UNKNOWN; + + scoped_refptr dispatcher; + MojoResult result = SharedBufferDispatcher::CreateFromPlatformSharedBuffer( + platform_buffer, &dispatcher); + if (result != MOJO_RESULT_OK) + return result; + MojoHandle h = AddDispatcher(dispatcher); + if (h == MOJO_HANDLE_INVALID) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + *mojo_wrapper_handle = h; + return MOJO_RESULT_OK; +} + +MojoResult Core::PassSharedMemoryHandle( + MojoHandle mojo_handle, + base::SharedMemoryHandle* shared_memory_handle, + size_t* num_bytes, + bool* read_only) { + if (!shared_memory_handle) + return MOJO_RESULT_INVALID_ARGUMENT; + + scoped_refptr dispatcher; + MojoResult result = MOJO_RESULT_OK; + { + base::AutoLock lock(handles_lock_); + // Get the dispatcher and check it before removing it from the handle table + // to ensure that the dispatcher is of the correct type. This ensures we + // don't close and remove the wrong type of dispatcher. + dispatcher = handles_.GetDispatcher(mojo_handle); + if (!dispatcher || dispatcher->GetType() != Dispatcher::Type::SHARED_BUFFER) + return MOJO_RESULT_INVALID_ARGUMENT; + + result = handles_.GetAndRemoveDispatcher(mojo_handle, &dispatcher); + if (result != MOJO_RESULT_OK) + return result; + } + + SharedBufferDispatcher* shm_dispatcher = + static_cast(dispatcher.get()); + scoped_refptr platform_shared_buffer = + shm_dispatcher->PassPlatformSharedBuffer(); + + if (!platform_shared_buffer) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (num_bytes) + *num_bytes = platform_shared_buffer->GetNumBytes(); + if (read_only) + *read_only = platform_shared_buffer->IsReadOnly(); + *shared_memory_handle = platform_shared_buffer->DuplicateSharedMemoryHandle(); + + shm_dispatcher->Close(); + return result; +} + +void Core::RequestShutdown(const base::Closure& callback) { + GetNodeController()->RequestShutdown(callback); +} + +ScopedMessagePipeHandle Core::CreateParentMessagePipe( + const std::string& token, const std::string& child_token) { + RequestContext request_context; + ports::PortRef port0, port1; + GetNodeController()->node()->CreatePortPair(&port0, &port1); + MojoHandle handle = AddDispatcher( + new MessagePipeDispatcher(GetNodeController(), port0, + kUnknownPipeIdForDebug, 0)); + GetNodeController()->ReservePort(token, port1, child_token); + return ScopedMessagePipeHandle(MessagePipeHandle(handle)); +} + +ScopedMessagePipeHandle Core::CreateChildMessagePipe(const std::string& token) { + RequestContext request_context; + ports::PortRef port0, port1; + GetNodeController()->node()->CreatePortPair(&port0, &port1); + MojoHandle handle = AddDispatcher( + new MessagePipeDispatcher(GetNodeController(), port0, + kUnknownPipeIdForDebug, 1)); + GetNodeController()->MergePortIntoParent(token, port1); + return ScopedMessagePipeHandle(MessagePipeHandle(handle)); +} + +MojoResult Core::SetProperty(MojoPropertyType type, const void* value) { + base::AutoLock locker(property_lock_); + switch (type) { + case MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED: + property_sync_call_allowed_ = *static_cast(value); + return MOJO_RESULT_OK; + default: + return MOJO_RESULT_INVALID_ARGUMENT; + } +} + +MojoTimeTicks Core::GetTimeTicksNow() { + return base::TimeTicks::Now().ToInternalValue(); +} + +MojoResult Core::Close(MojoHandle handle) { + RequestContext request_context; + scoped_refptr dispatcher; + { + base::AutoLock lock(handles_lock_); + MojoResult rv = handles_.GetAndRemoveDispatcher(handle, &dispatcher); + if (rv != MOJO_RESULT_OK) + return rv; + } + dispatcher->Close(); + return MOJO_RESULT_OK; +} + +MojoResult Core::QueryHandleSignalsState( + MojoHandle handle, + MojoHandleSignalsState* signals_state) { + RequestContext request_context; + scoped_refptr dispatcher = GetDispatcher(handle); + if (!dispatcher || !signals_state) + return MOJO_RESULT_INVALID_ARGUMENT; + *signals_state = dispatcher->GetHandleSignalsState(); + return MOJO_RESULT_OK; +} + +MojoResult Core::CreateWatcher(MojoWatcherCallback callback, + MojoHandle* watcher_handle) { + RequestContext request_context; + if (!watcher_handle) + return MOJO_RESULT_INVALID_ARGUMENT; + *watcher_handle = AddDispatcher(new WatcherDispatcher(callback)); + if (*watcher_handle == MOJO_HANDLE_INVALID) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + return MOJO_RESULT_OK; +} + +MojoResult Core::Watch(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context) { + RequestContext request_context; + scoped_refptr watcher = GetDispatcher(watcher_handle); + if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER) + return MOJO_RESULT_INVALID_ARGUMENT; + scoped_refptr dispatcher = GetDispatcher(handle); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + return watcher->WatchDispatcher(dispatcher, signals, context); +} + +MojoResult Core::CancelWatch(MojoHandle watcher_handle, uintptr_t context) { + RequestContext request_context; + scoped_refptr watcher = GetDispatcher(watcher_handle); + if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER) + return MOJO_RESULT_INVALID_ARGUMENT; + return watcher->CancelWatch(context); +} + +MojoResult Core::ArmWatcher(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + RequestContext request_context; + scoped_refptr watcher = GetDispatcher(watcher_handle); + if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER) + return MOJO_RESULT_INVALID_ARGUMENT; + return watcher->Arm(num_ready_contexts, ready_contexts, ready_results, + ready_signals_states); +} + +MojoResult Core::AllocMessage(uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoAllocMessageFlags flags, + MojoMessageHandle* message) { + if (!message) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (num_handles == 0) { // Fast path: no handles. + std::unique_ptr msg; + MojoResult rv = MessageForTransit::Create(&msg, num_bytes, nullptr, 0); + if (rv != MOJO_RESULT_OK) + return rv; + + *message = reinterpret_cast(msg.release()); + return MOJO_RESULT_OK; + } + + if (!handles) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (num_handles > kMaxHandlesPerMessage) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + std::vector dispatchers; + { + base::AutoLock lock(handles_lock_); + MojoResult rv = handles_.BeginTransit(handles, num_handles, &dispatchers); + if (rv != MOJO_RESULT_OK) { + handles_.CancelTransit(dispatchers); + return rv; + } + } + DCHECK_EQ(num_handles, dispatchers.size()); + + std::unique_ptr msg; + MojoResult rv = MessageForTransit::Create( + &msg, num_bytes, dispatchers.data(), num_handles); + + { + base::AutoLock lock(handles_lock_); + if (rv == MOJO_RESULT_OK) { + handles_.CompleteTransitAndClose(dispatchers); + *message = reinterpret_cast(msg.release()); + } else { + handles_.CancelTransit(dispatchers); + } + } + + return rv; +} + +MojoResult Core::FreeMessage(MojoMessageHandle message) { + if (!message) + return MOJO_RESULT_INVALID_ARGUMENT; + + delete reinterpret_cast(message); + + return MOJO_RESULT_OK; +} + +MojoResult Core::GetMessageBuffer(MojoMessageHandle message, void** buffer) { + if (!message) + return MOJO_RESULT_INVALID_ARGUMENT; + + *buffer = reinterpret_cast(message)->mutable_bytes(); + + return MOJO_RESULT_OK; +} + +MojoResult Core::GetProperty(MojoPropertyType type, void* value) { + base::AutoLock locker(property_lock_); + switch (type) { + case MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED: + *static_cast(value) = property_sync_call_allowed_; + return MOJO_RESULT_OK; + default: + return MOJO_RESULT_INVALID_ARGUMENT; + } +} + +MojoResult Core::CreateMessagePipe( + const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1) { + RequestContext request_context; + ports::PortRef port0, port1; + GetNodeController()->node()->CreatePortPair(&port0, &port1); + + CHECK(message_pipe_handle0); + CHECK(message_pipe_handle1); + + uint64_t pipe_id = base::RandUint64(); + + *message_pipe_handle0 = AddDispatcher( + new MessagePipeDispatcher(GetNodeController(), port0, pipe_id, 0)); + if (*message_pipe_handle0 == MOJO_HANDLE_INVALID) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + *message_pipe_handle1 = AddDispatcher( + new MessagePipeDispatcher(GetNodeController(), port1, pipe_id, 1)); + if (*message_pipe_handle1 == MOJO_HANDLE_INVALID) { + scoped_refptr unused; + unused->Close(); + + base::AutoLock lock(handles_lock_); + handles_.GetAndRemoveDispatcher(*message_pipe_handle0, &unused); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + return MOJO_RESULT_OK; +} + +MojoResult Core::WriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + if (num_bytes && !bytes) + return MOJO_RESULT_INVALID_ARGUMENT; + + MojoMessageHandle message; + MojoResult rv = AllocMessage(num_bytes, handles, num_handles, + MOJO_ALLOC_MESSAGE_FLAG_NONE, &message); + if (rv != MOJO_RESULT_OK) + return rv; + + if (num_bytes) { + void* buffer = nullptr; + rv = GetMessageBuffer(message, &buffer); + DCHECK_EQ(rv, MOJO_RESULT_OK); + memcpy(buffer, bytes, num_bytes); + } + + return WriteMessageNew(message_pipe_handle, message, flags); +} + +MojoResult Core::WriteMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags flags) { + RequestContext request_context; + std::unique_ptr message_for_transit( + reinterpret_cast(message)); + auto dispatcher = GetDispatcher(message_pipe_handle); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->WriteMessage(std::move(message_for_transit), flags); +} + +MojoResult Core::ReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + CHECK((!num_handles || !*num_handles || handles) && + (!num_bytes || !*num_bytes || bytes)); + RequestContext request_context; + auto dispatcher = GetDispatcher(message_pipe_handle); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + std::unique_ptr message; + MojoResult rv = + dispatcher->ReadMessage(&message, num_bytes, handles, num_handles, flags, + false /* ignore_num_bytes */); + if (rv != MOJO_RESULT_OK) + return rv; + + if (message && message->num_bytes()) + memcpy(bytes, message->bytes(), message->num_bytes()); + + return MOJO_RESULT_OK; +} + +MojoResult Core::ReadMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + CHECK(message); + CHECK(!num_handles || !*num_handles || handles); + RequestContext request_context; + auto dispatcher = GetDispatcher(message_pipe_handle); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + std::unique_ptr msg; + MojoResult rv = + dispatcher->ReadMessage(&msg, num_bytes, handles, num_handles, flags, + true /* ignore_num_bytes */); + if (rv != MOJO_RESULT_OK) + return rv; + *message = reinterpret_cast(msg.release()); + return MOJO_RESULT_OK; +} + +MojoResult Core::FuseMessagePipes(MojoHandle handle0, MojoHandle handle1) { + RequestContext request_context; + scoped_refptr dispatcher0; + scoped_refptr dispatcher1; + + bool valid_handles = true; + { + base::AutoLock lock(handles_lock_); + MojoResult result0 = handles_.GetAndRemoveDispatcher(handle0, &dispatcher0); + MojoResult result1 = handles_.GetAndRemoveDispatcher(handle1, &dispatcher1); + if (result0 != MOJO_RESULT_OK || result1 != MOJO_RESULT_OK || + dispatcher0->GetType() != Dispatcher::Type::MESSAGE_PIPE || + dispatcher1->GetType() != Dispatcher::Type::MESSAGE_PIPE) + valid_handles = false; + } + + if (!valid_handles) { + if (dispatcher0) + dispatcher0->Close(); + if (dispatcher1) + dispatcher1->Close(); + return MOJO_RESULT_INVALID_ARGUMENT; + } + + MessagePipeDispatcher* mpd0 = + static_cast(dispatcher0.get()); + MessagePipeDispatcher* mpd1 = + static_cast(dispatcher1.get()); + + if (!mpd0->Fuse(mpd1)) + return MOJO_RESULT_FAILED_PRECONDITION; + + return MOJO_RESULT_OK; +} + +MojoResult Core::NotifyBadMessage(MojoMessageHandle message, + const char* error, + size_t error_num_bytes) { + if (!message) + return MOJO_RESULT_INVALID_ARGUMENT; + + const PortsMessage& ports_message = + reinterpret_cast(message)->ports_message(); + if (ports_message.source_node() == ports::kInvalidNodeName) { + DVLOG(1) << "Received invalid message from unknown node."; + if (!default_process_error_callback_.is_null()) + default_process_error_callback_.Run(std::string(error, error_num_bytes)); + return MOJO_RESULT_OK; + } + + GetNodeController()->NotifyBadMessageFrom( + ports_message.source_node(), std::string(error, error_num_bytes)); + return MOJO_RESULT_OK; +} + +MojoResult Core::CreateDataPipe( + const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle) { + RequestContext request_context; + if (options && options->struct_size != sizeof(MojoCreateDataPipeOptions)) + return MOJO_RESULT_INVALID_ARGUMENT; + + MojoCreateDataPipeOptions create_options; + create_options.struct_size = sizeof(MojoCreateDataPipeOptions); + create_options.flags = options ? options->flags : 0; + create_options.element_num_bytes = options ? options->element_num_bytes : 1; + // TODO(rockot): Use Configuration to get default data pipe capacity. + create_options.capacity_num_bytes = + options && options->capacity_num_bytes ? options->capacity_num_bytes + : 64 * 1024; + + // TODO(rockot): Broker through the parent when necessary. + scoped_refptr ring_buffer = + GetNodeController()->CreateSharedBuffer( + create_options.capacity_num_bytes); + if (!ring_buffer) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + ports::PortRef port0, port1; + GetNodeController()->node()->CreatePortPair(&port0, &port1); + + CHECK(data_pipe_producer_handle); + CHECK(data_pipe_consumer_handle); + + uint64_t pipe_id = base::RandUint64(); + + scoped_refptr producer = new DataPipeProducerDispatcher( + GetNodeController(), port0, ring_buffer, create_options, + true /* initialized */, pipe_id); + scoped_refptr consumer = new DataPipeConsumerDispatcher( + GetNodeController(), port1, ring_buffer, create_options, + true /* initialized */, pipe_id); + + *data_pipe_producer_handle = AddDispatcher(producer); + *data_pipe_consumer_handle = AddDispatcher(consumer); + if (*data_pipe_producer_handle == MOJO_HANDLE_INVALID || + *data_pipe_consumer_handle == MOJO_HANDLE_INVALID) { + if (*data_pipe_producer_handle != MOJO_HANDLE_INVALID) { + scoped_refptr unused; + base::AutoLock lock(handles_lock_); + handles_.GetAndRemoveDispatcher(*data_pipe_producer_handle, &unused); + } + producer->Close(); + consumer->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + return MOJO_RESULT_OK; +} + +MojoResult Core::WriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_producer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->WriteData(elements, num_bytes, flags); +} + +MojoResult Core::BeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_producer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->BeginWriteData(buffer, buffer_num_bytes, flags); +} + +MojoResult Core::EndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_bytes_written) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_producer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->EndWriteData(num_bytes_written); +} + +MojoResult Core::ReadData(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_consumer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->ReadData(elements, num_bytes, flags); +} + +MojoResult Core::BeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_consumer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->BeginReadData(buffer, buffer_num_bytes, flags); +} + +MojoResult Core::EndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_bytes_read) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_consumer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->EndReadData(num_bytes_read); +} + +MojoResult Core::CreateSharedBuffer( + const MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle) { + RequestContext request_context; + MojoCreateSharedBufferOptions validated_options = {}; + MojoResult result = SharedBufferDispatcher::ValidateCreateOptions( + options, &validated_options); + if (result != MOJO_RESULT_OK) + return result; + + scoped_refptr dispatcher; + result = SharedBufferDispatcher::Create( + validated_options, GetNodeController(), num_bytes, &dispatcher); + if (result != MOJO_RESULT_OK) { + DCHECK(!dispatcher); + return result; + } + + *shared_buffer_handle = AddDispatcher(dispatcher); + if (*shared_buffer_handle == MOJO_HANDLE_INVALID) { + LOG(ERROR) << "Handle table full"; + dispatcher->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + return MOJO_RESULT_OK; +} + +MojoResult Core::DuplicateBufferHandle( + MojoHandle buffer_handle, + const MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle) { + RequestContext request_context; + scoped_refptr dispatcher(GetDispatcher(buffer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + // Don't verify |options| here; that's the dispatcher's job. + scoped_refptr new_dispatcher; + MojoResult result = + dispatcher->DuplicateBufferHandle(options, &new_dispatcher); + if (result != MOJO_RESULT_OK) + return result; + + *new_buffer_handle = AddDispatcher(new_dispatcher); + if (*new_buffer_handle == MOJO_HANDLE_INVALID) { + LOG(ERROR) << "Handle table full"; + dispatcher->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + return MOJO_RESULT_OK; +} + +MojoResult Core::MapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags) { + RequestContext request_context; + scoped_refptr dispatcher(GetDispatcher(buffer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + std::unique_ptr mapping; + MojoResult result = dispatcher->MapBuffer(offset, num_bytes, flags, &mapping); + if (result != MOJO_RESULT_OK) + return result; + + DCHECK(mapping); + void* address = mapping->GetBase(); + { + base::AutoLock locker(mapping_table_lock_); + result = mapping_table_.AddMapping(std::move(mapping)); + } + if (result != MOJO_RESULT_OK) + return result; + + *buffer = address; + return MOJO_RESULT_OK; +} + +MojoResult Core::UnmapBuffer(void* buffer) { + RequestContext request_context; + base::AutoLock lock(mapping_table_lock_); + return mapping_table_.RemoveMapping(buffer); +} + +MojoResult Core::WrapPlatformHandle(const MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle) { + ScopedPlatformHandle handle; + MojoResult result = MojoPlatformHandleToScopedPlatformHandle(platform_handle, + &handle); + if (result != MOJO_RESULT_OK) + return result; + + return CreatePlatformHandleWrapper(std::move(handle), mojo_handle); +} + +MojoResult Core::UnwrapPlatformHandle(MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle) { + ScopedPlatformHandle handle; + MojoResult result = PassWrappedPlatformHandle(mojo_handle, &handle); + if (result != MOJO_RESULT_OK) + return result; + + return ScopedPlatformHandleToMojoPlatformHandle(std::move(handle), + platform_handle); +} + +MojoResult Core::WrapPlatformSharedBufferHandle( + const MojoPlatformHandle* platform_handle, + size_t size, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle) { + DCHECK(size); + ScopedPlatformHandle handle; + MojoResult result = MojoPlatformHandleToScopedPlatformHandle(platform_handle, + &handle); + if (result != MOJO_RESULT_OK) + return result; + + bool read_only = flags & MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY; + scoped_refptr platform_buffer = + PlatformSharedBuffer::CreateFromPlatformHandle(size, read_only, + std::move(handle)); + if (!platform_buffer) + return MOJO_RESULT_UNKNOWN; + + scoped_refptr dispatcher; + result = SharedBufferDispatcher::CreateFromPlatformSharedBuffer( + platform_buffer, &dispatcher); + if (result != MOJO_RESULT_OK) + return result; + + MojoHandle h = AddDispatcher(dispatcher); + if (h == MOJO_HANDLE_INVALID) { + dispatcher->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + *mojo_handle = h; + return MOJO_RESULT_OK; +} + +MojoResult Core::UnwrapPlatformSharedBufferHandle( + MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle, + size_t* size, + MojoPlatformSharedBufferHandleFlags* flags) { + scoped_refptr dispatcher; + MojoResult result = MOJO_RESULT_OK; + { + base::AutoLock lock(handles_lock_); + result = handles_.GetAndRemoveDispatcher(mojo_handle, &dispatcher); + if (result != MOJO_RESULT_OK) + return result; + } + + if (dispatcher->GetType() != Dispatcher::Type::SHARED_BUFFER) { + dispatcher->Close(); + return MOJO_RESULT_INVALID_ARGUMENT; + } + + SharedBufferDispatcher* shm_dispatcher = + static_cast(dispatcher.get()); + scoped_refptr platform_shared_buffer = + shm_dispatcher->PassPlatformSharedBuffer(); + CHECK(platform_shared_buffer); + + CHECK(size); + *size = platform_shared_buffer->GetNumBytes(); + + CHECK(flags); + *flags = MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE; + if (platform_shared_buffer->IsReadOnly()) + *flags |= MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY; + + ScopedPlatformHandle handle = platform_shared_buffer->PassPlatformHandle(); + return ScopedPlatformHandleToMojoPlatformHandle(std::move(handle), + platform_handle); +} + +void Core::GetActiveHandlesForTest(std::vector* handles) { + base::AutoLock lock(handles_lock_); + handles_.GetActiveHandlesForTest(handles); +} + +// static +void Core::PassNodeControllerToIOThread( + std::unique_ptr node_controller) { + // It's OK to leak this reference. At this point we know the IO loop is still + // running, and we know the NodeController will observe its eventual + // destruction. This tells the NodeController to delete itself when that + // happens. + node_controller.release()->DestroyOnIOThreadShutdown(); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/core.h b/mojo/edk/system/core.h new file mode 100644 index 0000000000000000000000000000000000000000..1f6d865d2348e11c9ce267eb9cc0e2a52a3aa3bc --- /dev/null +++ b/mojo/edk/system/core.h @@ -0,0 +1,297 @@ +// Copyright 2013 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 MOJO_EDK_SYSTEM_CORE_H_ +#define MOJO_EDK_SYSTEM_CORE_H_ + +#include +#include +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/shared_memory_handle.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/handle_table.h" +#include "mojo/edk/system/mapping_table.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/platform_handle.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/c/system/watcher.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace base { +class PortProvider; +} + +namespace mojo { +namespace edk { + +// |Core| is an object that implements the Mojo system calls. All public methods +// are thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT Core { + public: + Core(); + virtual ~Core(); + + // Called exactly once, shortly after construction, and before any other + // methods are called on this object. + void SetIOTaskRunner(scoped_refptr io_task_runner); + + // Retrieves the NodeController for the current process. + NodeController* GetNodeController(); + + scoped_refptr GetDispatcher(MojoHandle handle); + + void SetDefaultProcessErrorCallback(const ProcessErrorCallback& callback); + + // Called in the parent process any time a new child is launched. + void AddChild(base::ProcessHandle process_handle, + ConnectionParams connection_params, + const std::string& child_token, + const ProcessErrorCallback& process_error_callback); + + // Called in the parent process when a child process fails to launch. + void ChildLaunchFailed(const std::string& child_token); + + // Called to connect to a peer process. This should be called only if there + // is no common ancestor for the processes involved within this mojo system. + // Both processes must call this function, each passing one end of a platform + // channel. This returns one end of a message pipe to each process. + ScopedMessagePipeHandle ConnectToPeerProcess(ScopedPlatformHandle pipe_handle, + const std::string& peer_token); + void ClosePeerConnection(const std::string& peer_token); + + // Called in a child process exactly once during early initialization. + void InitChild(ConnectionParams connection_params); + + // Creates a message pipe endpoint associated with |token|, which a child + // holding the token can later locate and connect to. + ScopedMessagePipeHandle CreateParentMessagePipe( + const std::string& token, const std::string& child_token); + + // Creates a message pipe endpoint and connects it to a pipe the parent has + // associated with |token|. + ScopedMessagePipeHandle CreateChildMessagePipe(const std::string& token); + + // Sets the mach port provider for this process. + void SetMachPortProvider(base::PortProvider* port_provider); + + MojoHandle AddDispatcher(scoped_refptr dispatcher); + + // Adds new dispatchers for non-message-pipe handles received in a message. + // |dispatchers| and |handles| should be the same size. + bool AddDispatchersFromTransit( + const std::vector& dispatchers, + MojoHandle* handles); + + // See "mojo/edk/embedder/embedder.h" for more information on these functions. + MojoResult CreatePlatformHandleWrapper(ScopedPlatformHandle platform_handle, + MojoHandle* wrapper_handle); + + MojoResult PassWrappedPlatformHandle(MojoHandle wrapper_handle, + ScopedPlatformHandle* platform_handle); + + MojoResult CreateSharedBufferWrapper( + base::SharedMemoryHandle shared_memory_handle, + size_t num_bytes, + bool read_only, + MojoHandle* mojo_wrapper_handle); + + MojoResult PassSharedMemoryHandle( + MojoHandle mojo_handle, + base::SharedMemoryHandle* shared_memory_handle, + size_t* num_bytes, + bool* read_only); + + // Requests that the EDK tear itself down. |callback| will be called once + // the shutdown process is complete. Note that |callback| is always called + // asynchronously on the calling thread if said thread is running a message + // loop, and the calling thread must continue running a MessageLoop at least + // until the callback is called. If there is no running loop, the |callback| + // may be called from any thread. Beware! + void RequestShutdown(const base::Closure& callback); + + MojoResult SetProperty(MojoPropertyType type, const void* value); + + // --------------------------------------------------------------------------- + + // The following methods are essentially implementations of the Mojo Core + // functions of the Mojo API, with the C interface translated to C++ by + // "mojo/edk/embedder/entrypoints.cc". The best way to understand the contract + // of these methods is to look at the header files defining the corresponding + // API functions, referenced below. + + // These methods correspond to the API functions defined in + // "mojo/public/c/system/functions.h": + MojoTimeTicks GetTimeTicksNow(); + MojoResult Close(MojoHandle handle); + MojoResult QueryHandleSignalsState(MojoHandle handle, + MojoHandleSignalsState* signals_state); + MojoResult CreateWatcher(MojoWatcherCallback callback, + MojoHandle* watcher_handle); + MojoResult Watch(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context); + MojoResult CancelWatch(MojoHandle watcher_handle, uintptr_t context); + MojoResult ArmWatcher(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states); + MojoResult AllocMessage(uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoAllocMessageFlags flags, + MojoMessageHandle* message); + MojoResult FreeMessage(MojoMessageHandle message); + MojoResult GetMessageBuffer(MojoMessageHandle message, void** buffer); + MojoResult GetProperty(MojoPropertyType type, void* value); + + // These methods correspond to the API functions defined in + // "mojo/public/c/system/message_pipe.h": + MojoResult CreateMessagePipe( + const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1); + MojoResult WriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags); + MojoResult WriteMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags flags); + MojoResult ReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags); + MojoResult ReadMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags); + MojoResult FuseMessagePipes(MojoHandle handle0, MojoHandle handle1); + MojoResult NotifyBadMessage(MojoMessageHandle message, + const char* error, + size_t error_num_bytes); + + // These methods correspond to the API functions defined in + // "mojo/public/c/system/data_pipe.h": + MojoResult CreateDataPipe( + const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle); + MojoResult WriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags); + MojoResult BeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags); + MojoResult EndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_bytes_written); + MojoResult ReadData(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags); + MojoResult BeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags); + MojoResult EndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_bytes_read); + + // These methods correspond to the API functions defined in + // "mojo/public/c/system/buffer.h": + MojoResult CreateSharedBuffer( + const MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle); + MojoResult DuplicateBufferHandle( + MojoHandle buffer_handle, + const MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle); + MojoResult MapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags); + MojoResult UnmapBuffer(void* buffer); + + // These methods correspond to the API functions defined in + // "mojo/public/c/system/platform_handle.h". + MojoResult WrapPlatformHandle(const MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle); + MojoResult UnwrapPlatformHandle(MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle); + MojoResult WrapPlatformSharedBufferHandle( + const MojoPlatformHandle* platform_handle, + size_t size, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle); + MojoResult UnwrapPlatformSharedBufferHandle( + MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle, + size_t* size, + MojoPlatformSharedBufferHandleFlags* flags); + + void GetActiveHandlesForTest(std::vector* handles); + + private: + // Used to pass ownership of our NodeController over to the IO thread in the + // event that we're torn down before said thread. + static void PassNodeControllerToIOThread( + std::unique_ptr node_controller); + + // Guards node_controller_. + // + // TODO(rockot): Consider removing this. It's only needed because we + // initialize node_controller_ lazily and that may happen on any thread. + // Otherwise it's effectively const and shouldn't need to be guarded. + // + // We can get rid of lazy initialization if we defer Mojo initialization far + // enough that zygotes don't do it. The zygote can't create a NodeController. + base::Lock node_controller_lock_; + + // This is lazily initialized on first access. Always use GetNodeController() + // to access it. + std::unique_ptr node_controller_; + + // The default callback to invoke, if any, when a process error is reported + // but cannot be associated with a specific process. + ProcessErrorCallback default_process_error_callback_; + + base::Lock handles_lock_; + HandleTable handles_; + + base::Lock mapping_table_lock_; // Protects |mapping_table_|. + MappingTable mapping_table_; + + base::Lock property_lock_; + // Properties that can be read using the MojoGetProperty() API. + bool property_sync_call_allowed_ = true; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_CORE_H_ diff --git a/mojo/edk/system/core_test_base.cc b/mojo/edk/system/core_test_base.cc new file mode 100644 index 0000000000000000000000000000000000000000..7751612e9d05f0b2e9dce3aa4d46c304eed83531 --- /dev/null +++ b/mojo/edk/system/core_test_base.cc @@ -0,0 +1,272 @@ +// Copyright 2013 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. + +#include "mojo/edk/system/core_test_base.h" + +#include +#include + +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/configuration.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/message_for_transit.h" + +namespace mojo { +namespace edk { +namespace test { + +namespace { + +// MockDispatcher -------------------------------------------------------------- + +class MockDispatcher : public Dispatcher { + public: + static scoped_refptr Create( + CoreTestBase::MockHandleInfo* info) { + return make_scoped_refptr(new MockDispatcher(info)); + } + + // Dispatcher: + Type GetType() const override { return Type::UNKNOWN; } + + MojoResult Close() override { + info_->IncrementCloseCallCount(); + return MOJO_RESULT_OK; + } + + MojoResult WriteMessage( + std::unique_ptr message, + MojoWriteMessageFlags /*flags*/) override { + info_->IncrementWriteMessageCallCount(); + + if (message->num_bytes() > GetConfiguration().max_message_num_bytes) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + if (message->num_handles()) + return MOJO_RESULT_UNIMPLEMENTED; + + return MOJO_RESULT_OK; + } + + MojoResult ReadMessage(std::unique_ptr* message, + uint32_t* num_bytes, + MojoHandle* handle, + uint32_t* num_handles, + MojoReadMessageFlags /*flags*/, + bool ignore_num_bytes) override { + info_->IncrementReadMessageCallCount(); + + if (num_handles) + *num_handles = 1; + + return MOJO_RESULT_OK; + } + + MojoResult WriteData(const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) override { + info_->IncrementWriteDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + MojoResult BeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) override { + info_->IncrementBeginWriteDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + MojoResult EndWriteData(uint32_t num_bytes_written) override { + info_->IncrementEndWriteDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + MojoResult ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) override { + info_->IncrementReadDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + MojoResult BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) override { + info_->IncrementBeginReadDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + MojoResult EndReadData(uint32_t num_bytes_read) override { + info_->IncrementEndReadDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + private: + explicit MockDispatcher(CoreTestBase::MockHandleInfo* info) : info_(info) { + CHECK(info_); + info_->IncrementCtorCallCount(); + } + + ~MockDispatcher() override { info_->IncrementDtorCallCount(); } + + CoreTestBase::MockHandleInfo* const info_; + + DISALLOW_COPY_AND_ASSIGN(MockDispatcher); +}; + +} // namespace + +// CoreTestBase ---------------------------------------------------------------- + +CoreTestBase::CoreTestBase() { +} + +CoreTestBase::~CoreTestBase() { +} + +MojoHandle CoreTestBase::CreateMockHandle(CoreTestBase::MockHandleInfo* info) { + scoped_refptr dispatcher = MockDispatcher::Create(info); + return core()->AddDispatcher(dispatcher); +} + +Core* CoreTestBase::core() { + return mojo::edk::internal::g_core; +} + +// CoreTestBase_MockHandleInfo ------------------------------------------------- + +CoreTestBase_MockHandleInfo::CoreTestBase_MockHandleInfo() + : ctor_call_count_(0), + dtor_call_count_(0), + close_call_count_(0), + write_message_call_count_(0), + read_message_call_count_(0), + write_data_call_count_(0), + begin_write_data_call_count_(0), + end_write_data_call_count_(0), + read_data_call_count_(0), + begin_read_data_call_count_(0), + end_read_data_call_count_(0) {} + +CoreTestBase_MockHandleInfo::~CoreTestBase_MockHandleInfo() { +} + +unsigned CoreTestBase_MockHandleInfo::GetCtorCallCount() const { + base::AutoLock locker(lock_); + return ctor_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetDtorCallCount() const { + base::AutoLock locker(lock_); + return dtor_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetCloseCallCount() const { + base::AutoLock locker(lock_); + return close_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetWriteMessageCallCount() const { + base::AutoLock locker(lock_); + return write_message_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetReadMessageCallCount() const { + base::AutoLock locker(lock_); + return read_message_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetWriteDataCallCount() const { + base::AutoLock locker(lock_); + return write_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetBeginWriteDataCallCount() const { + base::AutoLock locker(lock_); + return begin_write_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetEndWriteDataCallCount() const { + base::AutoLock locker(lock_); + return end_write_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetReadDataCallCount() const { + base::AutoLock locker(lock_); + return read_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetBeginReadDataCallCount() const { + base::AutoLock locker(lock_); + return begin_read_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetEndReadDataCallCount() const { + base::AutoLock locker(lock_); + return end_read_data_call_count_; +} + +void CoreTestBase_MockHandleInfo::IncrementCtorCallCount() { + base::AutoLock locker(lock_); + ctor_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementDtorCallCount() { + base::AutoLock locker(lock_); + dtor_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementCloseCallCount() { + base::AutoLock locker(lock_); + close_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementWriteMessageCallCount() { + base::AutoLock locker(lock_); + write_message_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementReadMessageCallCount() { + base::AutoLock locker(lock_); + read_message_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementWriteDataCallCount() { + base::AutoLock locker(lock_); + write_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementBeginWriteDataCallCount() { + base::AutoLock locker(lock_); + begin_write_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementEndWriteDataCallCount() { + base::AutoLock locker(lock_); + end_write_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementReadDataCallCount() { + base::AutoLock locker(lock_); + read_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementBeginReadDataCallCount() { + base::AutoLock locker(lock_); + begin_read_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementEndReadDataCallCount() { + base::AutoLock locker(lock_); + end_read_data_call_count_++; +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/core_test_base.h b/mojo/edk/system/core_test_base.h new file mode 100644 index 0000000000000000000000000000000000000000..3d156e32e2cfd0f71e1ce24cd317fe504af925a5 --- /dev/null +++ b/mojo/edk/system/core_test_base.h @@ -0,0 +1,94 @@ +// Copyright 2013 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 MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_ +#define MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_ + +#include + +#include "base/macros.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/public/c/system/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { + +class Core; + +namespace test { + +class CoreTestBase_MockHandleInfo; + +class CoreTestBase : public testing::Test { + public: + using MockHandleInfo = CoreTestBase_MockHandleInfo; + + CoreTestBase(); + ~CoreTestBase() override; + + protected: + // |info| must remain alive until the returned handle is closed. + MojoHandle CreateMockHandle(MockHandleInfo* info); + + Core* core(); + + private: + DISALLOW_COPY_AND_ASSIGN(CoreTestBase); +}; + +class CoreTestBase_MockHandleInfo { + public: + CoreTestBase_MockHandleInfo(); + ~CoreTestBase_MockHandleInfo(); + + unsigned GetCtorCallCount() const; + unsigned GetDtorCallCount() const; + unsigned GetCloseCallCount() const; + unsigned GetWriteMessageCallCount() const; + unsigned GetReadMessageCallCount() const; + unsigned GetWriteDataCallCount() const; + unsigned GetBeginWriteDataCallCount() const; + unsigned GetEndWriteDataCallCount() const; + unsigned GetReadDataCallCount() const; + unsigned GetBeginReadDataCallCount() const; + unsigned GetEndReadDataCallCount() const; + + // For use by |MockDispatcher|: + void IncrementCtorCallCount(); + void IncrementDtorCallCount(); + void IncrementCloseCallCount(); + void IncrementWriteMessageCallCount(); + void IncrementReadMessageCallCount(); + void IncrementWriteDataCallCount(); + void IncrementBeginWriteDataCallCount(); + void IncrementEndWriteDataCallCount(); + void IncrementReadDataCallCount(); + void IncrementBeginReadDataCallCount(); + void IncrementEndReadDataCallCount(); + + private: + mutable base::Lock lock_; // Protects the following members. + unsigned ctor_call_count_; + unsigned dtor_call_count_; + unsigned close_call_count_; + unsigned write_message_call_count_; + unsigned read_message_call_count_; + unsigned write_data_call_count_; + unsigned begin_write_data_call_count_; + unsigned end_write_data_call_count_; + unsigned read_data_call_count_; + unsigned begin_read_data_call_count_; + unsigned end_read_data_call_count_; + + DISALLOW_COPY_AND_ASSIGN(CoreTestBase_MockHandleInfo); +}; + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_ diff --git a/mojo/edk/system/core_unittest.cc b/mojo/edk/system/core_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..0d60b48a8bdb002271823ca233251e968fc1c217 --- /dev/null +++ b/mojo/edk/system/core_unittest.cc @@ -0,0 +1,971 @@ +// Copyright 2013 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. + +#include "mojo/edk/system/core.h" + +#include + +#include + +#include "base/bind.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/core_test_base.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/public/cpp/system/wait.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#endif + +namespace mojo { +namespace edk { +namespace { + +const MojoHandleSignalsState kEmptyMojoHandleSignalsState = {0u, 0u}; +const MojoHandleSignalsState kFullMojoHandleSignalsState = {~0u, ~0u}; +const MojoHandleSignals kAllSignals = MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED; + +using CoreTest = test::CoreTestBase; + +TEST_F(CoreTest, GetTimeTicksNow) { + const MojoTimeTicks start = core()->GetTimeTicksNow(); + ASSERT_NE(static_cast(0), start) + << "GetTimeTicksNow should return nonzero value"; + test::Sleep(test::DeadlineFromMilliseconds(15)); + const MojoTimeTicks finish = core()->GetTimeTicksNow(); + // Allow for some fuzz in sleep. + ASSERT_GE((finish - start), static_cast(8000)) + << "Sleeping should result in increasing time ticks"; +} + +TEST_F(CoreTest, Basic) { + MockHandleInfo info; + + ASSERT_EQ(0u, info.GetCtorCallCount()); + MojoHandle h = CreateMockHandle(&info); + ASSERT_EQ(1u, info.GetCtorCallCount()); + ASSERT_NE(h, MOJO_HANDLE_INVALID); + + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h, nullptr, 0, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteMessageCallCount()); + + ASSERT_EQ(0u, info.GetReadMessageCallCount()); + uint32_t num_bytes = 0; + ASSERT_EQ( + MOJO_RESULT_OK, + core()->ReadMessage(h, nullptr, &num_bytes, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetReadMessageCallCount()); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h, nullptr, nullptr, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(2u, info.GetReadMessageCallCount()); + + ASSERT_EQ(0u, info.GetWriteDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->WriteData(h, nullptr, nullptr, MOJO_WRITE_DATA_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteDataCallCount()); + + ASSERT_EQ(0u, info.GetBeginWriteDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->BeginWriteData(h, nullptr, nullptr, + MOJO_WRITE_DATA_FLAG_NONE)); + ASSERT_EQ(1u, info.GetBeginWriteDataCallCount()); + + ASSERT_EQ(0u, info.GetEndWriteDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, core()->EndWriteData(h, 0)); + ASSERT_EQ(1u, info.GetEndWriteDataCallCount()); + + ASSERT_EQ(0u, info.GetReadDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->ReadData(h, nullptr, nullptr, MOJO_READ_DATA_FLAG_NONE)); + ASSERT_EQ(1u, info.GetReadDataCallCount()); + + ASSERT_EQ(0u, info.GetBeginReadDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->BeginReadData(h, nullptr, nullptr, + MOJO_READ_DATA_FLAG_NONE)); + ASSERT_EQ(1u, info.GetBeginReadDataCallCount()); + + ASSERT_EQ(0u, info.GetEndReadDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, core()->EndReadData(h, 0)); + ASSERT_EQ(1u, info.GetEndReadDataCallCount()); + + ASSERT_EQ(0u, info.GetDtorCallCount()); + ASSERT_EQ(0u, info.GetCloseCallCount()); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h)); + ASSERT_EQ(1u, info.GetCloseCallCount()); + ASSERT_EQ(1u, info.GetDtorCallCount()); +} + +TEST_F(CoreTest, InvalidArguments) { + // |Close()|: + { + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(MOJO_HANDLE_INVALID)); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(10)); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(1000000000)); + + // Test a double-close. + MockHandleInfo info; + MojoHandle h = CreateMockHandle(&info); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h)); + ASSERT_EQ(1u, info.GetCloseCallCount()); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(h)); + ASSERT_EQ(1u, info.GetCloseCallCount()); + } + + // |CreateMessagePipe()|: Nothing to check (apart from things that cause + // death). + + // |WriteMessage()|: + // Only check arguments checked by |Core|, namely |handle|, |handles|, and + // |num_handles|. + { + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(MOJO_HANDLE_INVALID, nullptr, 0, + nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); + + MockHandleInfo info; + MojoHandle h = CreateMockHandle(&info); + MojoHandle handles[2] = {MOJO_HANDLE_INVALID, MOJO_HANDLE_INVALID}; + + // Huge handle count (implausibly big on some systems -- more than can be + // stored in a 32-bit address space). + // Note: This may return either |MOJO_RESULT_INVALID_ARGUMENT| or + // |MOJO_RESULT_RESOURCE_EXHAUSTED|, depending on whether it's plausible or + // not. + ASSERT_NE( + MOJO_RESULT_OK, + core()->WriteMessage(h, nullptr, 0, handles, + std::numeric_limits::max(), + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Null |bytes| with non-zero message size. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 1, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Null |handles| with non-zero handle count. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, nullptr, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Huge handle count (plausibly big). + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + core()->WriteMessage( + h, nullptr, 0, handles, + std::numeric_limits::max() / sizeof(handles[0]), + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Invalid handle in |handles|. + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, handles, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Two invalid handles in |handles|. + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Can't send a handle over itself. Note that this will also cause |h| to be + // closed. + handles[0] = h; + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, handles, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + h = CreateMockHandle(&info); + + MockHandleInfo info2; + + // This is "okay", but |MockDispatcher| doesn't implement it. + handles[0] = CreateMockHandle(&info2); + ASSERT_EQ( + MOJO_RESULT_UNIMPLEMENTED, + core()->WriteMessage(h, nullptr, 0, handles, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteMessageCallCount()); + + // One of the |handles| is still invalid. + handles[0] = CreateMockHandle(&info2); + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteMessageCallCount()); + + // One of the |handles| is the same as |h|. Both handles are closed. + handles[0] = CreateMockHandle(&info2); + handles[1] = h; + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteMessageCallCount()); + + h = CreateMockHandle(&info); + + // Can't send a handle twice in the same message. + handles[0] = CreateMockHandle(&info2); + handles[1] = handles[0]; + ASSERT_EQ( + MOJO_RESULT_BUSY, + core()->WriteMessage(h, nullptr, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteMessageCallCount()); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h)); + } + + // |ReadMessage()|: + // Only check arguments checked by |Core|, namely |handle|, |handles|, and + // |num_handles|. + { + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->ReadMessage(MOJO_HANDLE_INVALID, nullptr, nullptr, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE)); + + MockHandleInfo info; + MojoHandle h = CreateMockHandle(&info); + + // Okay. + uint32_t handle_count = 0; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h, nullptr, nullptr, nullptr, &handle_count, + MOJO_READ_MESSAGE_FLAG_NONE)); + // Checked by |Core|, shouldn't go through to the dispatcher. + ASSERT_EQ(1u, info.GetReadMessageCallCount()); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h)); + } +} + +// These test invalid arguments that should cause death if we're being paranoid +// about checking arguments (which we would want to do if, e.g., we were in a +// true "kernel" situation, but we might not want to do otherwise for +// performance reasons). Probably blatant errors like passing in null pointers +// (for required pointer arguments) will still cause death, but perhaps not +// predictably. +TEST_F(CoreTest, InvalidArgumentsDeath) { +#if defined(OFFICIAL_BUILD) + const char kMemoryCheckFailedRegex[] = ""; +#else + const char kMemoryCheckFailedRegex[] = "Check failed"; +#endif + + // |CreateMessagePipe()|: + { + MojoHandle h; + ASSERT_DEATH_IF_SUPPORTED( + core()->CreateMessagePipe(nullptr, nullptr, nullptr), + kMemoryCheckFailedRegex); + ASSERT_DEATH_IF_SUPPORTED( + core()->CreateMessagePipe(nullptr, &h, nullptr), + kMemoryCheckFailedRegex); + ASSERT_DEATH_IF_SUPPORTED( + core()->CreateMessagePipe(nullptr, nullptr, &h), + kMemoryCheckFailedRegex); + } + + // |ReadMessage()|: + // Only check arguments checked by |Core|, namely |handle|, |handles|, and + // |num_handles|. + { + MockHandleInfo info; + MojoHandle h = CreateMockHandle(&info); + + uint32_t handle_count = 1; + ASSERT_DEATH_IF_SUPPORTED( + core()->ReadMessage(h, nullptr, nullptr, nullptr, &handle_count, + MOJO_READ_MESSAGE_FLAG_NONE), + kMemoryCheckFailedRegex); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h)); + } +} + +TEST_F(CoreTest, MessagePipe) { + MojoHandle h[2]; + MojoHandleSignalsState hss[2]; + + ASSERT_EQ(MOJO_RESULT_OK, core()->CreateMessagePipe(nullptr, &h[0], &h[1])); + // Should get two distinct, valid handles. + ASSERT_NE(h[0], MOJO_HANDLE_INVALID); + ASSERT_NE(h[1], MOJO_HANDLE_INVALID); + ASSERT_NE(h[0], h[1]); + + // Neither should be readable. + hss[0] = kEmptyMojoHandleSignalsState; + hss[1] = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[0], &hss[0])); + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[1], &hss[1])); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals); + ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals); + ASSERT_EQ(kAllSignals, hss[1].satisfiable_signals); + + // Try to read anyway. + char buffer[1] = {'a'}; + uint32_t buffer_size = 1; + ASSERT_EQ( + MOJO_RESULT_SHOULD_WAIT, + core()->ReadMessage(h[0], buffer, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + // Check that it left its inputs alone. + ASSERT_EQ('a', buffer[0]); + ASSERT_EQ(1u, buffer_size); + + // Write to |h[1]|. + buffer[0] = 'b'; + ASSERT_EQ( + MOJO_RESULT_OK, + core()->WriteMessage(h[1], buffer, 1, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for |h[0]| to become readable. + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h[0]), + MOJO_HANDLE_SIGNAL_READABLE, &hss[0])); + + // Read from |h[0]|. + // First, get only the size. + buffer_size = 0; + ASSERT_EQ( + MOJO_RESULT_RESOURCE_EXHAUSTED, + core()->ReadMessage(h[0], nullptr, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, buffer_size); + // Then actually read it. + buffer[0] = 'c'; + buffer_size = 1; + ASSERT_EQ( + MOJO_RESULT_OK, + core()->ReadMessage(h[0], buffer, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ('b', buffer[0]); + ASSERT_EQ(1u, buffer_size); + + // |h[0]| should no longer be readable. + hss[0] = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[0], &hss[0])); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals); + ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals); + + // Write to |h[0]|. + buffer[0] = 'd'; + ASSERT_EQ( + MOJO_RESULT_OK, + core()->WriteMessage(h[0], buffer, 1, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Close |h[0]|. + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h[0])); + + // Wait for |h[1]| to learn about the other end's closure. + EXPECT_EQ( + MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(h[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss[1])); + + // Check that |h[1]| is no longer writable (and will never be). + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfiable_signals); + + // Check that |h[1]| is still readable (for the moment). + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfiable_signals); + + // Discard a message from |h[1]|. + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + core()->ReadMessage(h[1], nullptr, nullptr, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); + + // |h[1]| is no longer readable (and will never be). + hss[1] = kFullMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[1], &hss[1])); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[1].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[1].satisfiable_signals); + + // Try writing to |h[1]|. + buffer[0] = 'e'; + ASSERT_EQ( + MOJO_RESULT_FAILED_PRECONDITION, + core()->WriteMessage(h[1], buffer, 1, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h[1])); +} + +// Tests passing a message pipe handle. +TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing1) { + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast(sizeof(kHello)); + const char kWorld[] = "world!!!"; + const uint32_t kWorldSize = static_cast(sizeof(kWorld)); + char buffer[100]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t num_bytes; + MojoHandle handles[10]; + uint32_t num_handles; + MojoHandleSignalsState hss; + MojoHandle h_received; + + MojoHandle h_passing[2]; + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(nullptr, &h_passing[0], &h_passing[1])); + + // Make sure that |h_passing[]| work properly. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + ASSERT_EQ(0u, num_handles); + + // Make sure that you can't pass either of the message pipe's handles over + // itself. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, + &h_passing[0], 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(nullptr, &h_passing[0], &h_passing[1])); + + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, + &h_passing[1], 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(nullptr, &h_passing[0], &h_passing[1])); + + MojoHandle h_passed[2]; + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(nullptr, &h_passed[0], &h_passed[1])); + + // Make sure that |h_passed[]| work properly. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passed[0], kHello, kHelloSize, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passed[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passed[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + ASSERT_EQ(0u, num_handles); + + // Send |h_passed[1]| from |h_passing[0]| to |h_passing[1]|. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kWorld, kWorldSize, + &h_passed[1], 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kWorldSize, num_bytes); + ASSERT_STREQ(kWorld, buffer); + ASSERT_EQ(1u, num_handles); + h_received = handles[0]; + ASSERT_NE(h_received, MOJO_HANDLE_INVALID); + ASSERT_NE(h_received, h_passing[0]); + ASSERT_NE(h_received, h_passing[1]); + ASSERT_NE(h_received, h_passed[0]); + + // Note: We rely on the Mojo system not re-using handle values very often. + ASSERT_NE(h_received, h_passed[1]); + + // |h_passed[1]| should no longer be valid; check that trying to close it + // fails. See above note. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(h_passed[1])); + + // Write to |h_passed[0]|. Should receive on |h_received|. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passed[0], kHello, kHelloSize, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_received), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_received, buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + ASSERT_EQ(0u, num_handles); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[0])); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[1])); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passed[0])); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_received)); +} + +TEST_F(CoreTest, DataPipe) { + MojoHandle ph, ch; // p is for producer and c is for consumer. + MojoHandleSignalsState hss; + + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateDataPipe(nullptr, &ph, &ch)); + // Should get two distinct, valid handles. + ASSERT_NE(ph, MOJO_HANDLE_INVALID); + ASSERT_NE(ch, MOJO_HANDLE_INVALID); + ASSERT_NE(ph, ch); + + // Producer should be never-readable, but already writable. + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ph, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Consumer should be never-writable, and not yet readable. + hss = kFullMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); + EXPECT_EQ(0u, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Write. + signed char elements[2] = {'A', 'B'}; + uint32_t num_bytes = 2u; + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteData(ph, elements, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + ASSERT_EQ(2u, num_bytes); + + // Wait for the data to arrive to the consumer. + EXPECT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + + // Consumer should now be readable. + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Peek one character. + elements[0] = -1; + elements[1] = -1; + num_bytes = 1u; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadData( + ch, elements, &num_bytes, + MOJO_READ_DATA_FLAG_NONE | MOJO_READ_DATA_FLAG_PEEK)); + ASSERT_EQ('A', elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Read one character. + elements[0] = -1; + elements[1] = -1; + num_bytes = 1u; + ASSERT_EQ(MOJO_RESULT_OK, core()->ReadData(ch, elements, &num_bytes, + MOJO_READ_DATA_FLAG_NONE)); + ASSERT_EQ('A', elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Two-phase write. + void* write_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginWriteData(ph, &write_ptr, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + // We count on the default options providing a decent buffer size. + ASSERT_GE(num_bytes, 3u); + + // Trying to do a normal write during a two-phase write should fail. + elements[0] = 'X'; + num_bytes = 1u; + ASSERT_EQ(MOJO_RESULT_BUSY, + core()->WriteData(ph, elements, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + + // Actually write the data, and complete it now. + static_cast(write_ptr)[0] = 'C'; + static_cast(write_ptr)[1] = 'D'; + static_cast(write_ptr)[2] = 'E'; + ASSERT_EQ(MOJO_RESULT_OK, core()->EndWriteData(ph, 3u)); + + // Wait for the data to arrive to the consumer. + ASSERT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + + // Query how much data we have. + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadData(ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_QUERY)); + ASSERT_GE(num_bytes, 1u); + + // Try to query with peek. Should fail. + num_bytes = 0; + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->ReadData(ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_QUERY | MOJO_READ_DATA_FLAG_PEEK)); + ASSERT_EQ(0u, num_bytes); + + // Try to discard ten characters, in all-or-none mode. Should fail. + num_bytes = 10; + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, + core()->ReadData( + ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // Try to discard two characters, in peek mode. Should fail. + num_bytes = 2; + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->ReadData(ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_PEEK)); + + // Discard a character. + num_bytes = 1; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadData( + ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // Ensure the 3 bytes were read. + ASSERT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + + // Try a two-phase read of the remaining three bytes with peek. Should fail. + const void* read_ptr = nullptr; + num_bytes = 3; + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->BeginReadData(ch, &read_ptr, &num_bytes, + MOJO_READ_DATA_FLAG_PEEK)); + + // Read the remaining two characters, in two-phase mode (all-or-none). + num_bytes = 3; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginReadData(ch, &read_ptr, &num_bytes, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + // Note: Count on still being able to do the contiguous read here. + ASSERT_EQ(3u, num_bytes); + + // Discarding right now should fail. + num_bytes = 1; + ASSERT_EQ(MOJO_RESULT_BUSY, + core()->ReadData(ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_DISCARD)); + + // Actually check our data and end the two-phase read. + ASSERT_EQ('C', static_cast(read_ptr)[0]); + ASSERT_EQ('D', static_cast(read_ptr)[1]); + ASSERT_EQ('E', static_cast(read_ptr)[2]); + ASSERT_EQ(MOJO_RESULT_OK, core()->EndReadData(ch, 3u)); + + // Consumer should now be no longer readable. + hss = kFullMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); + EXPECT_EQ(0u, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // TODO(vtl): More. + + // Close the producer. + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(ph)); + + // Wait for this to get to the consumer. + EXPECT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + + // The consumer should now be never-readable. + hss = kFullMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(ch)); +} + +// Tests passing data pipe producer and consumer handles. +TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing2) { + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast(sizeof(kHello)); + const char kWorld[] = "world!!!"; + const uint32_t kWorldSize = static_cast(sizeof(kWorld)); + char buffer[100]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t num_bytes; + MojoHandle handles[10]; + uint32_t num_handles; + MojoHandleSignalsState hss; + + MojoHandle h_passing[2]; + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(nullptr, &h_passing[0], &h_passing[1])); + + MojoHandle ph, ch; + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateDataPipe(nullptr, &ph, &ch)); + + // Send |ch| from |h_passing[0]| to |h_passing[1]|. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + ASSERT_EQ(1u, num_handles); + MojoHandle ch_received = handles[0]; + ASSERT_NE(ch_received, MOJO_HANDLE_INVALID); + ASSERT_NE(ch_received, h_passing[0]); + ASSERT_NE(ch_received, h_passing[1]); + ASSERT_NE(ch_received, ph); + + // Note: We rely on the Mojo system not re-using handle values very often. + ASSERT_NE(ch_received, ch); + + // |ch| should no longer be valid; check that trying to close it fails. See + // above note. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(ch)); + + // Write to |ph|. Should receive on |ch_received|. + num_bytes = kWorldSize; + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteData(ph, kWorld, &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(ch_received), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + num_bytes = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadData(ch_received, buffer, &num_bytes, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kWorldSize, num_bytes); + ASSERT_STREQ(kWorld, buffer); + + // Now pass |ph| in the same direction. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kWorld, kWorldSize, &ph, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kWorldSize, num_bytes); + ASSERT_STREQ(kWorld, buffer); + ASSERT_EQ(1u, num_handles); + MojoHandle ph_received = handles[0]; + ASSERT_NE(ph_received, MOJO_HANDLE_INVALID); + ASSERT_NE(ph_received, h_passing[0]); + ASSERT_NE(ph_received, h_passing[1]); + ASSERT_NE(ph_received, ch_received); + + // Again, rely on the Mojo system not re-using handle values very often. + ASSERT_NE(ph_received, ph); + + // |ph| should no longer be valid; check that trying to close it fails. See + // above note. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(ph)); + + // Write to |ph_received|. Should receive on |ch_received|. + num_bytes = kHelloSize; + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteData(ph_received, kHello, &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(ch_received), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + num_bytes = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadData(ch_received, buffer, &num_bytes, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + + ph = ph_received; + ph_received = MOJO_HANDLE_INVALID; + ch = ch_received; + ch_received = MOJO_HANDLE_INVALID; + + // Make sure that |ph| can't be sent if it's in a two-phase write. + void* write_ptr = nullptr; + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginWriteData(ph, &write_ptr, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + ASSERT_GE(num_bytes, 1u); + ASSERT_EQ(MOJO_RESULT_BUSY, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ph, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // But |ch| can, even if |ph| is in a two-phase write. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ch = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE)); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + ASSERT_EQ(1u, num_handles); + ch = handles[0]; + ASSERT_NE(ch, MOJO_HANDLE_INVALID); + + // Complete the two-phase write. + static_cast(write_ptr)[0] = 'x'; + ASSERT_EQ(MOJO_RESULT_OK, core()->EndWriteData(ph, 1)); + + // Wait for |ch| to be readable. + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Make sure that |ch| can't be sent if it's in a two-phase read. + const void* read_ptr = nullptr; + num_bytes = 1; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginReadData(ch, &read_ptr, &num_bytes, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + ASSERT_EQ(MOJO_RESULT_BUSY, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // But |ph| can, even if |ch| is in a two-phase read. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kWorld, kWorldSize, &ph, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ph = MOJO_HANDLE_INVALID; + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kWorldSize, num_bytes); + ASSERT_STREQ(kWorld, buffer); + ASSERT_EQ(1u, num_handles); + ph = handles[0]; + ASSERT_NE(ph, MOJO_HANDLE_INVALID); + + // Complete the two-phase read. + ASSERT_EQ('x', static_cast(read_ptr)[0]); + ASSERT_EQ(MOJO_RESULT_OK, core()->EndReadData(ch, 1)); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[0])); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[1])); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(ph)); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(ch)); +} + +struct TestAsyncWaiter { + TestAsyncWaiter() : result(MOJO_RESULT_UNKNOWN) {} + + void Awake(MojoResult r) { result = r; } + + MojoResult result; +}; + +// TODO(vtl): Test |DuplicateBufferHandle()| and |MapBuffer()|. + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.cc b/mojo/edk/system/data_pipe_consumer_dispatcher.cc new file mode 100644 index 0000000000000000000000000000000000000000..f3387324fc6e4c16a546ddb89b4d69c094cf7df5 --- /dev/null +++ b/mojo/edk/system/data_pipe_consumer_dispatcher.cc @@ -0,0 +1,562 @@ +// Copyright 2013 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. + +#include "mojo/edk/system/data_pipe_consumer_dispatcher.h" + +#include +#include + +#include +#include +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/data_pipe_control_message.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/ports_message.h" +#include "mojo/edk/system/request_context.h" +#include "mojo/public/c/system/data_pipe.h" + +namespace mojo { +namespace edk { + +namespace { + +const uint8_t kFlagPeerClosed = 0x01; + +#pragma pack(push, 1) + +struct SerializedState { + MojoCreateDataPipeOptions options; + uint64_t pipe_id; + uint32_t read_offset; + uint32_t bytes_available; + uint8_t flags; + char padding[7]; +}; + +static_assert(sizeof(SerializedState) % 8 == 0, + "Invalid SerializedState size."); + +#pragma pack(pop) + +} // namespace + +// A PortObserver which forwards to a DataPipeConsumerDispatcher. This owns a +// reference to the dispatcher to ensure it lives as long as the observed port. +class DataPipeConsumerDispatcher::PortObserverThunk + : public NodeController::PortObserver { + public: + explicit PortObserverThunk( + scoped_refptr dispatcher) + : dispatcher_(dispatcher) {} + + private: + ~PortObserverThunk() override {} + + // NodeController::PortObserver: + void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); } + + scoped_refptr dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(PortObserverThunk); +}; + +DataPipeConsumerDispatcher::DataPipeConsumerDispatcher( + NodeController* node_controller, + const ports::PortRef& control_port, + scoped_refptr shared_ring_buffer, + const MojoCreateDataPipeOptions& options, + bool initialized, + uint64_t pipe_id) + : options_(options), + node_controller_(node_controller), + control_port_(control_port), + pipe_id_(pipe_id), + watchers_(this), + shared_ring_buffer_(shared_ring_buffer) { + if (initialized) { + base::AutoLock lock(lock_); + InitializeNoLock(); + } +} + +Dispatcher::Type DataPipeConsumerDispatcher::GetType() const { + return Type::DATA_PIPE_CONSUMER; +} + +MojoResult DataPipeConsumerDispatcher::Close() { + base::AutoLock lock(lock_); + DVLOG(1) << "Closing data pipe consumer " << pipe_id_; + return CloseNoLock(); +} + +MojoResult DataPipeConsumerDispatcher::ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + base::AutoLock lock(lock_); + + if (!shared_ring_buffer_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (in_two_phase_read_) + return MOJO_RESULT_BUSY; + + const bool had_new_data = new_data_available_; + new_data_available_ = false; + + if ((flags & MOJO_READ_DATA_FLAG_QUERY)) { + if ((flags & MOJO_READ_DATA_FLAG_PEEK) || + (flags & MOJO_READ_DATA_FLAG_DISCARD)) + return MOJO_RESULT_INVALID_ARGUMENT; + DCHECK(!(flags & MOJO_READ_DATA_FLAG_DISCARD)); // Handled above. + DVLOG_IF(2, elements) + << "Query mode: ignoring non-null |elements|"; + *num_bytes = static_cast(bytes_available_); + + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + return MOJO_RESULT_OK; + } + + bool discard = false; + if ((flags & MOJO_READ_DATA_FLAG_DISCARD)) { + // These flags are mutally exclusive. + if (flags & MOJO_READ_DATA_FLAG_PEEK) + return MOJO_RESULT_INVALID_ARGUMENT; + DVLOG_IF(2, elements) + << "Discard mode: ignoring non-null |elements|"; + discard = true; + } + + uint32_t max_num_bytes_to_read = *num_bytes; + if (max_num_bytes_to_read % options_.element_num_bytes != 0) + return MOJO_RESULT_INVALID_ARGUMENT; + + bool all_or_none = flags & MOJO_READ_DATA_FLAG_ALL_OR_NONE; + uint32_t min_num_bytes_to_read = + all_or_none ? max_num_bytes_to_read : 0; + + if (min_num_bytes_to_read > bytes_available_) { + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION + : MOJO_RESULT_OUT_OF_RANGE; + } + + uint32_t bytes_to_read = std::min(max_num_bytes_to_read, bytes_available_); + if (bytes_to_read == 0) { + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION + : MOJO_RESULT_SHOULD_WAIT; + } + + if (!discard) { + uint8_t* data = static_cast(ring_buffer_mapping_->GetBase()); + CHECK(data); + + uint8_t* destination = static_cast(elements); + CHECK(destination); + + DCHECK_LE(read_offset_, options_.capacity_num_bytes); + uint32_t tail_bytes_to_copy = + std::min(options_.capacity_num_bytes - read_offset_, bytes_to_read); + uint32_t head_bytes_to_copy = bytes_to_read - tail_bytes_to_copy; + if (tail_bytes_to_copy > 0) + memcpy(destination, data + read_offset_, tail_bytes_to_copy); + if (head_bytes_to_copy > 0) + memcpy(destination + tail_bytes_to_copy, data, head_bytes_to_copy); + } + *num_bytes = bytes_to_read; + + bool peek = !!(flags & MOJO_READ_DATA_FLAG_PEEK); + if (discard || !peek) { + read_offset_ = (read_offset_ + bytes_to_read) % options_.capacity_num_bytes; + bytes_available_ -= bytes_to_read; + + base::AutoUnlock unlock(lock_); + NotifyRead(bytes_to_read); + } + + // We may have just read the last available data and thus changed the signals + // state. + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + + return MOJO_RESULT_OK; +} + +MojoResult DataPipeConsumerDispatcher::BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + base::AutoLock lock(lock_); + if (!shared_ring_buffer_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (in_two_phase_read_) + return MOJO_RESULT_BUSY; + + // These flags may not be used in two-phase mode. + if ((flags & MOJO_READ_DATA_FLAG_DISCARD) || + (flags & MOJO_READ_DATA_FLAG_QUERY) || + (flags & MOJO_READ_DATA_FLAG_PEEK)) + return MOJO_RESULT_INVALID_ARGUMENT; + + const bool had_new_data = new_data_available_; + new_data_available_ = false; + + if (bytes_available_ == 0) { + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION + : MOJO_RESULT_SHOULD_WAIT; + } + + DCHECK_LT(read_offset_, options_.capacity_num_bytes); + uint32_t bytes_to_read = std::min(bytes_available_, + options_.capacity_num_bytes - read_offset_); + + CHECK(ring_buffer_mapping_); + uint8_t* data = static_cast(ring_buffer_mapping_->GetBase()); + CHECK(data); + + in_two_phase_read_ = true; + *buffer = data + read_offset_; + *buffer_num_bytes = bytes_to_read; + two_phase_max_bytes_read_ = bytes_to_read; + + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + + return MOJO_RESULT_OK; +} + +MojoResult DataPipeConsumerDispatcher::EndReadData(uint32_t num_bytes_read) { + base::AutoLock lock(lock_); + if (!in_two_phase_read_) + return MOJO_RESULT_FAILED_PRECONDITION; + + if (in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + CHECK(shared_ring_buffer_); + + MojoResult rv; + if (num_bytes_read > two_phase_max_bytes_read_ || + num_bytes_read % options_.element_num_bytes != 0) { + rv = MOJO_RESULT_INVALID_ARGUMENT; + } else { + rv = MOJO_RESULT_OK; + read_offset_ = + (read_offset_ + num_bytes_read) % options_.capacity_num_bytes; + + DCHECK_GE(bytes_available_, num_bytes_read); + bytes_available_ -= num_bytes_read; + + base::AutoUnlock unlock(lock_); + NotifyRead(num_bytes_read); + } + + in_two_phase_read_ = false; + two_phase_max_bytes_read_ = 0; + + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + + return rv; +} + +HandleSignalsState DataPipeConsumerDispatcher::GetHandleSignalsState() const { + base::AutoLock lock(lock_); + return GetHandleSignalsStateNoLock(); +} + +MojoResult DataPipeConsumerDispatcher::AddWatcherRef( + const scoped_refptr& watcher, + uintptr_t context) { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock()); +} + +MojoResult DataPipeConsumerDispatcher::RemoveWatcherRef( + WatcherDispatcher* watcher, + uintptr_t context) { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Remove(watcher, context); +} + +void DataPipeConsumerDispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) { + base::AutoLock lock(lock_); + DCHECK(in_transit_); + *num_bytes = static_cast(sizeof(SerializedState)); + *num_ports = 1; + *num_handles = 1; +} + +bool DataPipeConsumerDispatcher::EndSerialize( + void* destination, + ports::PortName* ports, + PlatformHandle* platform_handles) { + SerializedState* state = static_cast(destination); + memcpy(&state->options, &options_, sizeof(MojoCreateDataPipeOptions)); + memset(state->padding, 0, sizeof(state->padding)); + + base::AutoLock lock(lock_); + DCHECK(in_transit_); + state->pipe_id = pipe_id_; + state->read_offset = read_offset_; + state->bytes_available = bytes_available_; + state->flags = peer_closed_ ? kFlagPeerClosed : 0; + + ports[0] = control_port_.name(); + + buffer_handle_for_transit_ = shared_ring_buffer_->DuplicatePlatformHandle(); + platform_handles[0] = buffer_handle_for_transit_.get(); + + return true; +} + +bool DataPipeConsumerDispatcher::BeginTransit() { + base::AutoLock lock(lock_); + if (in_transit_) + return false; + in_transit_ = !in_two_phase_read_; + return in_transit_; +} + +void DataPipeConsumerDispatcher::CompleteTransitAndClose() { + node_controller_->SetPortObserver(control_port_, nullptr); + + base::AutoLock lock(lock_); + DCHECK(in_transit_); + in_transit_ = false; + transferred_ = true; + ignore_result(buffer_handle_for_transit_.release()); + CloseNoLock(); +} + +void DataPipeConsumerDispatcher::CancelTransit() { + base::AutoLock lock(lock_); + DCHECK(in_transit_); + in_transit_ = false; + buffer_handle_for_transit_.reset(); + UpdateSignalsStateNoLock(); +} + +// static +scoped_refptr +DataPipeConsumerDispatcher::Deserialize(const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles) { + if (num_ports != 1 || num_handles != 1 || + num_bytes != sizeof(SerializedState)) { + return nullptr; + } + + const SerializedState* state = static_cast(data); + + NodeController* node_controller = internal::g_core->GetNodeController(); + ports::PortRef port; + if (node_controller->node()->GetPort(ports[0], &port) != ports::OK) + return nullptr; + + PlatformHandle buffer_handle; + std::swap(buffer_handle, handles[0]); + scoped_refptr ring_buffer = + PlatformSharedBuffer::CreateFromPlatformHandle( + state->options.capacity_num_bytes, + false /* read_only */, + ScopedPlatformHandle(buffer_handle)); + if (!ring_buffer) { + DLOG(ERROR) << "Failed to deserialize shared buffer handle."; + return nullptr; + } + + scoped_refptr dispatcher = + new DataPipeConsumerDispatcher(node_controller, port, ring_buffer, + state->options, false /* initialized */, + state->pipe_id); + + { + base::AutoLock lock(dispatcher->lock_); + dispatcher->read_offset_ = state->read_offset; + dispatcher->bytes_available_ = state->bytes_available; + dispatcher->new_data_available_ = state->bytes_available > 0; + dispatcher->peer_closed_ = state->flags & kFlagPeerClosed; + dispatcher->InitializeNoLock(); + dispatcher->UpdateSignalsStateNoLock(); + } + + return dispatcher; +} + +DataPipeConsumerDispatcher::~DataPipeConsumerDispatcher() { + DCHECK(is_closed_ && !shared_ring_buffer_ && !ring_buffer_mapping_ && + !in_transit_); +} + +void DataPipeConsumerDispatcher::InitializeNoLock() { + lock_.AssertAcquired(); + + if (shared_ring_buffer_) { + DCHECK(!ring_buffer_mapping_); + ring_buffer_mapping_ = + shared_ring_buffer_->Map(0, options_.capacity_num_bytes); + if (!ring_buffer_mapping_) { + DLOG(ERROR) << "Failed to map shared buffer."; + shared_ring_buffer_ = nullptr; + } + } + + base::AutoUnlock unlock(lock_); + node_controller_->SetPortObserver( + control_port_, + make_scoped_refptr(new PortObserverThunk(this))); +} + +MojoResult DataPipeConsumerDispatcher::CloseNoLock() { + lock_.AssertAcquired(); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + is_closed_ = true; + ring_buffer_mapping_.reset(); + shared_ring_buffer_ = nullptr; + + watchers_.NotifyClosed(); + if (!transferred_) { + base::AutoUnlock unlock(lock_); + node_controller_->ClosePort(control_port_); + } + + return MOJO_RESULT_OK; +} + +HandleSignalsState +DataPipeConsumerDispatcher::GetHandleSignalsStateNoLock() const { + lock_.AssertAcquired(); + + HandleSignalsState rv; + if (shared_ring_buffer_ && bytes_available_) { + if (!in_two_phase_read_) { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_READABLE; + if (new_data_available_) + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE; + } + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + } else if (!peer_closed_ && shared_ring_buffer_) { + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + } + + if (shared_ring_buffer_) { + if (new_data_available_ || !peer_closed_) + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE; + } + + if (peer_closed_) + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + + return rv; +} + +void DataPipeConsumerDispatcher::NotifyRead(uint32_t num_bytes) { + DVLOG(1) << "Data pipe consumer " << pipe_id_ << " notifying peer: " + << num_bytes << " bytes read. [control_port=" + << control_port_.name() << "]"; + + SendDataPipeControlMessage(node_controller_, control_port_, + DataPipeCommand::DATA_WAS_READ, num_bytes); +} + +void DataPipeConsumerDispatcher::OnPortStatusChanged() { + DCHECK(RequestContext::current()); + + base::AutoLock lock(lock_); + + // We stop observing the control port as soon it's transferred, but this can + // race with events which are raised right before that happens. This is fine + // to ignore. + if (transferred_) + return; + + DVLOG(1) << "Control port status changed for data pipe producer " << pipe_id_; + + UpdateSignalsStateNoLock(); +} + +void DataPipeConsumerDispatcher::UpdateSignalsStateNoLock() { + lock_.AssertAcquired(); + + bool was_peer_closed = peer_closed_; + size_t previous_bytes_available = bytes_available_; + + ports::PortStatus port_status; + int rv = node_controller_->node()->GetStatus(control_port_, &port_status); + if (rv != ports::OK || !port_status.receiving_messages) { + DVLOG(1) << "Data pipe consumer " << pipe_id_ << " is aware of peer closure" + << " [control_port=" << control_port_.name() << "]"; + peer_closed_ = true; + } else if (rv == ports::OK && port_status.has_messages && !in_transit_) { + ports::ScopedMessage message; + do { + int rv = node_controller_->node()->GetMessage( + control_port_, &message, nullptr); + if (rv != ports::OK) + peer_closed_ = true; + if (message) { + if (message->num_payload_bytes() < sizeof(DataPipeControlMessage)) { + peer_closed_ = true; + break; + } + + const DataPipeControlMessage* m = + static_cast( + message->payload_bytes()); + + if (m->command != DataPipeCommand::DATA_WAS_WRITTEN) { + DLOG(ERROR) << "Unexpected control message from producer."; + peer_closed_ = true; + break; + } + + if (static_cast(bytes_available_) + m->num_bytes > + options_.capacity_num_bytes) { + DLOG(ERROR) << "Producer claims to have written too many bytes."; + peer_closed_ = true; + break; + } + + DVLOG(1) << "Data pipe consumer " << pipe_id_ << " is aware that " + << m->num_bytes << " bytes were written. [control_port=" + << control_port_.name() << "]"; + + bytes_available_ += m->num_bytes; + } + } while (message); + } + + bool has_new_data = bytes_available_ != previous_bytes_available; + if (has_new_data) + new_data_available_ = true; + + if (peer_closed_ != was_peer_closed || has_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.h b/mojo/edk/system/data_pipe_consumer_dispatcher.h new file mode 100644 index 0000000000000000000000000000000000000000..120c7a387f8a7126cd65754a9f3e7fc7d3712a07 --- /dev/null +++ b/mojo/edk/system/data_pipe_consumer_dispatcher.h @@ -0,0 +1,123 @@ +// Copyright 2013 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 MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/ports/port_ref.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/edk/system/watcher_set.h" + +namespace mojo { +namespace edk { + +class NodeController; + +// This is the Dispatcher implementation for the consumer handle for data +// pipes created by the Mojo primitive MojoCreateDataPipe(). This class is +// thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT DataPipeConsumerDispatcher final + : public Dispatcher { + public: + DataPipeConsumerDispatcher( + NodeController* node_controller, + const ports::PortRef& control_port, + scoped_refptr shared_ring_buffer, + const MojoCreateDataPipeOptions& options, + bool initialized, + uint64_t pipe_id); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + MojoResult ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) override; + MojoResult BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) override; + MojoResult EndReadData(uint32_t num_bytes_read) override; + HandleSignalsState GetHandleSignalsState() const override; + MojoResult AddWatcherRef(const scoped_refptr& watcher, + uintptr_t context) override; + MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) override; + void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) override; + bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) override; + bool BeginTransit() override; + void CompleteTransitAndClose() override; + void CancelTransit() override; + + static scoped_refptr + Deserialize(const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles); + + private: + class PortObserverThunk; + friend class PortObserverThunk; + + ~DataPipeConsumerDispatcher() override; + + void InitializeNoLock(); + MojoResult CloseNoLock(); + HandleSignalsState GetHandleSignalsStateNoLock() const; + void NotifyRead(uint32_t num_bytes); + void OnPortStatusChanged(); + void UpdateSignalsStateNoLock(); + + const MojoCreateDataPipeOptions options_; + NodeController* const node_controller_; + const ports::PortRef control_port_; + const uint64_t pipe_id_; + + // Guards access to the fields below. + mutable base::Lock lock_; + + WatcherSet watchers_; + + scoped_refptr shared_ring_buffer_; + std::unique_ptr ring_buffer_mapping_; + ScopedPlatformHandle buffer_handle_for_transit_; + + bool in_two_phase_read_ = false; + uint32_t two_phase_max_bytes_read_ = 0; + + bool in_transit_ = false; + bool is_closed_ = false; + bool peer_closed_ = false; + bool transferred_ = false; + + uint32_t read_offset_ = 0; + uint32_t bytes_available_ = 0; + + // Indicates whether any new data is available since the last read attempt. + bool new_data_available_ = false; + + DISALLOW_COPY_AND_ASSIGN(DataPipeConsumerDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_ diff --git a/mojo/edk/system/data_pipe_control_message.cc b/mojo/edk/system/data_pipe_control_message.cc new file mode 100644 index 0000000000000000000000000000000000000000..23873b829035c743245e361f646b698dcaf4a22a --- /dev/null +++ b/mojo/edk/system/data_pipe_control_message.cc @@ -0,0 +1,35 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/data_pipe_control_message.h" + +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/ports_message.h" + +namespace mojo { +namespace edk { + +void SendDataPipeControlMessage(NodeController* node_controller, + const ports::PortRef& port, + DataPipeCommand command, + uint32_t num_bytes) { + std::unique_ptr message = + PortsMessage::NewUserMessage(sizeof(DataPipeControlMessage), 0, 0); + CHECK(message); + + DataPipeControlMessage* data = + static_cast(message->mutable_payload_bytes()); + data->command = command; + data->num_bytes = num_bytes; + + int rv = node_controller->SendMessage(port, std::move(message)); + if (rv != ports::OK && rv != ports::ERROR_PORT_PEER_CLOSED) { + DLOG(ERROR) << "Unexpected failure sending data pipe control message: " + << rv; + } +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/data_pipe_control_message.h b/mojo/edk/system/data_pipe_control_message.h new file mode 100644 index 0000000000000000000000000000000000000000..ec84ea3c559c626d413721d5efacc76a043aacf2 --- /dev/null +++ b/mojo/edk/system/data_pipe_control_message.h @@ -0,0 +1,43 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_DATA_PIPE_CONTROL_MESSAGE_H_ +#define MOJO_EDK_SYSTEM_DATA_PIPE_CONTROL_MESSAGE_H_ + +#include + +#include + +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/ports/port_ref.h" +#include "mojo/public/c/system/macros.h" + +namespace mojo { +namespace edk { + +class NodeController; + +enum DataPipeCommand : uint32_t { + // Signal to the consumer that new data is available. + DATA_WAS_WRITTEN, + + // Signal to the producer that data has been consumed. + DATA_WAS_READ, +}; + +// Message header for messages sent over a data pipe control port. +struct MOJO_ALIGNAS(8) DataPipeControlMessage { + DataPipeCommand command; + uint32_t num_bytes; +}; + +void SendDataPipeControlMessage(NodeController* node_controller, + const ports::PortRef& port, + DataPipeCommand command, + uint32_t num_bytes); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_DATA_PIPE_CONTROL_MESSAGE_H_ diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.cc b/mojo/edk/system/data_pipe_producer_dispatcher.cc new file mode 100644 index 0000000000000000000000000000000000000000..b0102a6d9b007ff6d8a1489fce9e4a38445a77d2 --- /dev/null +++ b/mojo/edk/system/data_pipe_producer_dispatcher.cc @@ -0,0 +1,507 @@ +// Copyright 2013 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. + +#include "mojo/edk/system/data_pipe_producer_dispatcher.h" + +#include +#include + +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/configuration.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/data_pipe_control_message.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/ports_message.h" +#include "mojo/edk/system/request_context.h" +#include "mojo/public/c/system/data_pipe.h" + +namespace mojo { +namespace edk { + +namespace { + +const uint8_t kFlagPeerClosed = 0x01; + +#pragma pack(push, 1) + +struct SerializedState { + MojoCreateDataPipeOptions options; + uint64_t pipe_id; + uint32_t write_offset; + uint32_t available_capacity; + uint8_t flags; + char padding[7]; +}; + +static_assert(sizeof(SerializedState) % 8 == 0, + "Invalid SerializedState size."); + +#pragma pack(pop) + +} // namespace + +// A PortObserver which forwards to a DataPipeProducerDispatcher. This owns a +// reference to the dispatcher to ensure it lives as long as the observed port. +class DataPipeProducerDispatcher::PortObserverThunk + : public NodeController::PortObserver { + public: + explicit PortObserverThunk( + scoped_refptr dispatcher) + : dispatcher_(dispatcher) {} + + private: + ~PortObserverThunk() override {} + + // NodeController::PortObserver: + void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); } + + scoped_refptr dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(PortObserverThunk); +}; + +DataPipeProducerDispatcher::DataPipeProducerDispatcher( + NodeController* node_controller, + const ports::PortRef& control_port, + scoped_refptr shared_ring_buffer, + const MojoCreateDataPipeOptions& options, + bool initialized, + uint64_t pipe_id) + : options_(options), + node_controller_(node_controller), + control_port_(control_port), + pipe_id_(pipe_id), + watchers_(this), + shared_ring_buffer_(shared_ring_buffer), + available_capacity_(options_.capacity_num_bytes) { + if (initialized) { + base::AutoLock lock(lock_); + InitializeNoLock(); + } +} + +Dispatcher::Type DataPipeProducerDispatcher::GetType() const { + return Type::DATA_PIPE_PRODUCER; +} + +MojoResult DataPipeProducerDispatcher::Close() { + base::AutoLock lock(lock_); + DVLOG(1) << "Closing data pipe producer " << pipe_id_; + return CloseNoLock(); +} + +MojoResult DataPipeProducerDispatcher::WriteData(const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + base::AutoLock lock(lock_); + if (!shared_ring_buffer_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (in_two_phase_write_) + return MOJO_RESULT_BUSY; + + if (peer_closed_) + return MOJO_RESULT_FAILED_PRECONDITION; + + if (*num_bytes % options_.element_num_bytes != 0) + return MOJO_RESULT_INVALID_ARGUMENT; + if (*num_bytes == 0) + return MOJO_RESULT_OK; // Nothing to do. + + if ((flags & MOJO_WRITE_DATA_FLAG_ALL_OR_NONE) && + (*num_bytes > available_capacity_)) { + // Don't return "should wait" since you can't wait for a specified amount of + // data. + return MOJO_RESULT_OUT_OF_RANGE; + } + + DCHECK_LE(available_capacity_, options_.capacity_num_bytes); + uint32_t num_bytes_to_write = std::min(*num_bytes, available_capacity_); + if (num_bytes_to_write == 0) + return MOJO_RESULT_SHOULD_WAIT; + + *num_bytes = num_bytes_to_write; + + CHECK(ring_buffer_mapping_); + uint8_t* data = static_cast(ring_buffer_mapping_->GetBase()); + CHECK(data); + + const uint8_t* source = static_cast(elements); + CHECK(source); + + DCHECK_LE(write_offset_, options_.capacity_num_bytes); + uint32_t tail_bytes_to_write = + std::min(options_.capacity_num_bytes - write_offset_, + num_bytes_to_write); + uint32_t head_bytes_to_write = num_bytes_to_write - tail_bytes_to_write; + + DCHECK_GT(tail_bytes_to_write, 0u); + memcpy(data + write_offset_, source, tail_bytes_to_write); + if (head_bytes_to_write > 0) + memcpy(data, source + tail_bytes_to_write, head_bytes_to_write); + + DCHECK_LE(num_bytes_to_write, available_capacity_); + available_capacity_ -= num_bytes_to_write; + write_offset_ = (write_offset_ + num_bytes_to_write) % + options_.capacity_num_bytes; + + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + + base::AutoUnlock unlock(lock_); + NotifyWrite(num_bytes_to_write); + + return MOJO_RESULT_OK; +} + +MojoResult DataPipeProducerDispatcher::BeginWriteData( + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + base::AutoLock lock(lock_); + if (!shared_ring_buffer_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + // These flags may not be used in two-phase mode. + if (flags & MOJO_WRITE_DATA_FLAG_ALL_OR_NONE) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (in_two_phase_write_) + return MOJO_RESULT_BUSY; + if (peer_closed_) + return MOJO_RESULT_FAILED_PRECONDITION; + + if (available_capacity_ == 0) { + return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION + : MOJO_RESULT_SHOULD_WAIT; + } + + in_two_phase_write_ = true; + *buffer_num_bytes = std::min(options_.capacity_num_bytes - write_offset_, + available_capacity_); + DCHECK_GT(*buffer_num_bytes, 0u); + + CHECK(ring_buffer_mapping_); + uint8_t* data = static_cast(ring_buffer_mapping_->GetBase()); + *buffer = data + write_offset_; + + return MOJO_RESULT_OK; +} + +MojoResult DataPipeProducerDispatcher::EndWriteData( + uint32_t num_bytes_written) { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (!in_two_phase_write_) + return MOJO_RESULT_FAILED_PRECONDITION; + + DCHECK(shared_ring_buffer_); + DCHECK(ring_buffer_mapping_); + + // Note: Allow successful completion of the two-phase write even if the other + // side has been closed. + MojoResult rv = MOJO_RESULT_OK; + if (num_bytes_written > available_capacity_ || + num_bytes_written % options_.element_num_bytes != 0 || + write_offset_ + num_bytes_written > options_.capacity_num_bytes) { + rv = MOJO_RESULT_INVALID_ARGUMENT; + } else { + DCHECK_LE(num_bytes_written + write_offset_, options_.capacity_num_bytes); + available_capacity_ -= num_bytes_written; + write_offset_ = (write_offset_ + num_bytes_written) % + options_.capacity_num_bytes; + + base::AutoUnlock unlock(lock_); + NotifyWrite(num_bytes_written); + } + + in_two_phase_write_ = false; + + // If we're now writable, we *became* writable (since we weren't writable + // during the two-phase write), so notify watchers. + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + + return rv; +} + +HandleSignalsState DataPipeProducerDispatcher::GetHandleSignalsState() const { + base::AutoLock lock(lock_); + return GetHandleSignalsStateNoLock(); +} + +MojoResult DataPipeProducerDispatcher::AddWatcherRef( + const scoped_refptr& watcher, + uintptr_t context) { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock()); +} + +MojoResult DataPipeProducerDispatcher::RemoveWatcherRef( + WatcherDispatcher* watcher, + uintptr_t context) { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Remove(watcher, context); +} + +void DataPipeProducerDispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) { + base::AutoLock lock(lock_); + DCHECK(in_transit_); + *num_bytes = sizeof(SerializedState); + *num_ports = 1; + *num_handles = 1; +} + +bool DataPipeProducerDispatcher::EndSerialize( + void* destination, + ports::PortName* ports, + PlatformHandle* platform_handles) { + SerializedState* state = static_cast(destination); + memcpy(&state->options, &options_, sizeof(MojoCreateDataPipeOptions)); + memset(state->padding, 0, sizeof(state->padding)); + + base::AutoLock lock(lock_); + DCHECK(in_transit_); + state->pipe_id = pipe_id_; + state->write_offset = write_offset_; + state->available_capacity = available_capacity_; + state->flags = peer_closed_ ? kFlagPeerClosed : 0; + + ports[0] = control_port_.name(); + + buffer_handle_for_transit_ = shared_ring_buffer_->DuplicatePlatformHandle(); + platform_handles[0] = buffer_handle_for_transit_.get(); + + return true; +} + +bool DataPipeProducerDispatcher::BeginTransit() { + base::AutoLock lock(lock_); + if (in_transit_) + return false; + in_transit_ = !in_two_phase_write_; + return in_transit_; +} + +void DataPipeProducerDispatcher::CompleteTransitAndClose() { + node_controller_->SetPortObserver(control_port_, nullptr); + + base::AutoLock lock(lock_); + DCHECK(in_transit_); + transferred_ = true; + in_transit_ = false; + ignore_result(buffer_handle_for_transit_.release()); + CloseNoLock(); +} + +void DataPipeProducerDispatcher::CancelTransit() { + base::AutoLock lock(lock_); + DCHECK(in_transit_); + in_transit_ = false; + buffer_handle_for_transit_.reset(); + + HandleSignalsState state = GetHandleSignalsStateNoLock(); + watchers_.NotifyState(state); +} + +// static +scoped_refptr +DataPipeProducerDispatcher::Deserialize(const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles) { + if (num_ports != 1 || num_handles != 1 || + num_bytes != sizeof(SerializedState)) { + return nullptr; + } + + const SerializedState* state = static_cast(data); + + NodeController* node_controller = internal::g_core->GetNodeController(); + ports::PortRef port; + if (node_controller->node()->GetPort(ports[0], &port) != ports::OK) + return nullptr; + + PlatformHandle buffer_handle; + std::swap(buffer_handle, handles[0]); + scoped_refptr ring_buffer = + PlatformSharedBuffer::CreateFromPlatformHandle( + state->options.capacity_num_bytes, + false /* read_only */, + ScopedPlatformHandle(buffer_handle)); + if (!ring_buffer) { + DLOG(ERROR) << "Failed to deserialize shared buffer handle."; + return nullptr; + } + + scoped_refptr dispatcher = + new DataPipeProducerDispatcher(node_controller, port, ring_buffer, + state->options, false /* initialized */, + state->pipe_id); + + { + base::AutoLock lock(dispatcher->lock_); + dispatcher->write_offset_ = state->write_offset; + dispatcher->available_capacity_ = state->available_capacity; + dispatcher->peer_closed_ = state->flags & kFlagPeerClosed; + dispatcher->InitializeNoLock(); + dispatcher->UpdateSignalsStateNoLock(); + } + + return dispatcher; +} + +DataPipeProducerDispatcher::~DataPipeProducerDispatcher() { + DCHECK(is_closed_ && !in_transit_ && !shared_ring_buffer_ && + !ring_buffer_mapping_); +} + +void DataPipeProducerDispatcher::InitializeNoLock() { + lock_.AssertAcquired(); + + if (shared_ring_buffer_) { + ring_buffer_mapping_ = + shared_ring_buffer_->Map(0, options_.capacity_num_bytes); + if (!ring_buffer_mapping_) { + DLOG(ERROR) << "Failed to map shared buffer."; + shared_ring_buffer_ = nullptr; + } + } + + base::AutoUnlock unlock(lock_); + node_controller_->SetPortObserver( + control_port_, + make_scoped_refptr(new PortObserverThunk(this))); +} + +MojoResult DataPipeProducerDispatcher::CloseNoLock() { + lock_.AssertAcquired(); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + is_closed_ = true; + ring_buffer_mapping_.reset(); + shared_ring_buffer_ = nullptr; + + watchers_.NotifyClosed(); + if (!transferred_) { + base::AutoUnlock unlock(lock_); + node_controller_->ClosePort(control_port_); + } + + return MOJO_RESULT_OK; +} + +HandleSignalsState DataPipeProducerDispatcher::GetHandleSignalsStateNoLock() + const { + lock_.AssertAcquired(); + HandleSignalsState rv; + if (!peer_closed_) { + if (!in_two_phase_write_ && shared_ring_buffer_ && available_capacity_ > 0) + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; + } else { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + } + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + return rv; +} + +void DataPipeProducerDispatcher::NotifyWrite(uint32_t num_bytes) { + DVLOG(1) << "Data pipe producer " << pipe_id_ << " notifying peer: " + << num_bytes << " bytes written. [control_port=" + << control_port_.name() << "]"; + + SendDataPipeControlMessage(node_controller_, control_port_, + DataPipeCommand::DATA_WAS_WRITTEN, num_bytes); +} + +void DataPipeProducerDispatcher::OnPortStatusChanged() { + DCHECK(RequestContext::current()); + + base::AutoLock lock(lock_); + + // We stop observing the control port as soon it's transferred, but this can + // race with events which are raised right before that happens. This is fine + // to ignore. + if (transferred_) + return; + + DVLOG(1) << "Control port status changed for data pipe producer " << pipe_id_; + + UpdateSignalsStateNoLock(); +} + +void DataPipeProducerDispatcher::UpdateSignalsStateNoLock() { + lock_.AssertAcquired(); + + bool was_peer_closed = peer_closed_; + size_t previous_capacity = available_capacity_; + + ports::PortStatus port_status; + int rv = node_controller_->node()->GetStatus(control_port_, &port_status); + if (rv != ports::OK || !port_status.receiving_messages) { + DVLOG(1) << "Data pipe producer " << pipe_id_ << " is aware of peer closure" + << " [control_port=" << control_port_.name() << "]"; + peer_closed_ = true; + } else if (rv == ports::OK && port_status.has_messages && !in_transit_) { + ports::ScopedMessage message; + do { + int rv = node_controller_->node()->GetMessage( + control_port_, &message, nullptr); + if (rv != ports::OK) + peer_closed_ = true; + if (message) { + if (message->num_payload_bytes() < sizeof(DataPipeControlMessage)) { + peer_closed_ = true; + break; + } + + const DataPipeControlMessage* m = + static_cast( + message->payload_bytes()); + + if (m->command != DataPipeCommand::DATA_WAS_READ) { + DLOG(ERROR) << "Unexpected message from consumer."; + peer_closed_ = true; + break; + } + + if (static_cast(available_capacity_) + m->num_bytes > + options_.capacity_num_bytes) { + DLOG(ERROR) << "Consumer claims to have read too many bytes."; + break; + } + + DVLOG(1) << "Data pipe producer " << pipe_id_ << " is aware that " + << m->num_bytes << " bytes were read. [control_port=" + << control_port_.name() << "]"; + + available_capacity_ += m->num_bytes; + } + } while (message); + } + + if (peer_closed_ != was_peer_closed || + available_capacity_ != previous_capacity) { + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + } +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.h b/mojo/edk/system/data_pipe_producer_dispatcher.h new file mode 100644 index 0000000000000000000000000000000000000000..1eddd5dfa889e1c0184825957c2f120fab58040d --- /dev/null +++ b/mojo/edk/system/data_pipe_producer_dispatcher.h @@ -0,0 +1,123 @@ +// Copyright 2013 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 MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/ports/port_ref.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/edk/system/watcher_set.h" + +namespace mojo { +namespace edk { + +struct DataPipeControlMessage; +class NodeController; + +// This is the Dispatcher implementation for the producer handle for data +// pipes created by the Mojo primitive MojoCreateDataPipe(). This class is +// thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT DataPipeProducerDispatcher final + : public Dispatcher { + public: + DataPipeProducerDispatcher( + NodeController* node_controller, + const ports::PortRef& port, + scoped_refptr shared_ring_buffer, + const MojoCreateDataPipeOptions& options, + bool initialized, + uint64_t pipe_id); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + MojoResult WriteData(const void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) override; + MojoResult BeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) override; + MojoResult EndWriteData(uint32_t num_bytes_written) override; + HandleSignalsState GetHandleSignalsState() const override; + MojoResult AddWatcherRef(const scoped_refptr& watcher, + uintptr_t context) override; + MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) override; + void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) override; + bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) override; + bool BeginTransit() override; + void CompleteTransitAndClose() override; + void CancelTransit() override; + + static scoped_refptr + Deserialize(const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles); + + private: + class PortObserverThunk; + friend class PortObserverThunk; + + ~DataPipeProducerDispatcher() override; + + void OnSharedBufferCreated(const scoped_refptr& buffer); + void InitializeNoLock(); + MojoResult CloseNoLock(); + HandleSignalsState GetHandleSignalsStateNoLock() const; + void NotifyWrite(uint32_t num_bytes); + void OnPortStatusChanged(); + void UpdateSignalsStateNoLock(); + bool ProcessMessageNoLock(const DataPipeControlMessage& message, + ScopedPlatformHandleVectorPtr handles); + + const MojoCreateDataPipeOptions options_; + NodeController* const node_controller_; + const ports::PortRef control_port_; + const uint64_t pipe_id_; + + // Guards access to the fields below. + mutable base::Lock lock_; + + WatcherSet watchers_; + + bool buffer_requested_ = false; + + scoped_refptr shared_ring_buffer_; + std::unique_ptr ring_buffer_mapping_; + ScopedPlatformHandle buffer_handle_for_transit_; + + bool in_transit_ = false; + bool is_closed_ = false; + bool peer_closed_ = false; + bool transferred_ = false; + bool in_two_phase_write_ = false; + + uint32_t write_offset_ = 0; + uint32_t available_capacity_; + + DISALLOW_COPY_AND_ASSIGN(DataPipeProducerDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_ diff --git a/mojo/edk/system/data_pipe_unittest.cc b/mojo/edk/system/data_pipe_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..79c1f758fba5a883b369328335c45a0971016ec6 --- /dev/null +++ b/mojo/edk/system/data_pipe_unittest.cc @@ -0,0 +1,2034 @@ +// 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. + +#include +#include + +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/cpp/system/simple_watcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +const uint32_t kSizeOfOptions = + static_cast(sizeof(MojoCreateDataPipeOptions)); + +// In various places, we have to poll (since, e.g., we can't yet wait for a +// certain amount of data to be available). This is the maximum number of +// iterations (separated by a short sleep). +// TODO(vtl): Get rid of this. +const size_t kMaxPoll = 100; + +// Used in Multiprocess test. +const size_t kMultiprocessCapacity = 37; +const char kMultiprocessTestData[] = "hello i'm a string that is 36 bytes"; +const int kMultiprocessMaxIter = 5; + +// TODO(rockot): There are many uses of ASSERT where EXPECT would be more +// appropriate. Fix this. + +class DataPipeTest : public test::MojoTestBase { + public: + DataPipeTest() : producer_(MOJO_HANDLE_INVALID), + consumer_(MOJO_HANDLE_INVALID) {} + + ~DataPipeTest() override { + if (producer_ != MOJO_HANDLE_INVALID) + CHECK_EQ(MOJO_RESULT_OK, MojoClose(producer_)); + if (consumer_ != MOJO_HANDLE_INVALID) + CHECK_EQ(MOJO_RESULT_OK, MojoClose(consumer_)); + } + + MojoResult Create(const MojoCreateDataPipeOptions* options) { + return MojoCreateDataPipe(options, &producer_, &consumer_); + } + + MojoResult WriteData(const void* elements, + uint32_t* num_bytes, + bool all_or_none = false) { + return MojoWriteData(producer_, elements, num_bytes, + all_or_none ? MOJO_WRITE_DATA_FLAG_ALL_OR_NONE + : MOJO_WRITE_DATA_FLAG_NONE); + } + + MojoResult ReadData(void* elements, + uint32_t* num_bytes, + bool all_or_none = false, + bool peek = false) { + MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_NONE; + if (all_or_none) + flags |= MOJO_READ_DATA_FLAG_ALL_OR_NONE; + if (peek) + flags |= MOJO_READ_DATA_FLAG_PEEK; + return MojoReadData(consumer_, elements, num_bytes, flags); + } + + MojoResult QueryData(uint32_t* num_bytes) { + return MojoReadData(consumer_, nullptr, num_bytes, + MOJO_READ_DATA_FLAG_QUERY); + } + + MojoResult DiscardData(uint32_t* num_bytes, bool all_or_none = false) { + MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_DISCARD; + if (all_or_none) + flags |= MOJO_READ_DATA_FLAG_ALL_OR_NONE; + return MojoReadData(consumer_, nullptr, num_bytes, flags); + } + + MojoResult BeginReadData(const void** elements, + uint32_t* num_bytes, + bool all_or_none = false) { + MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_NONE; + if (all_or_none) + flags |= MOJO_READ_DATA_FLAG_ALL_OR_NONE; + return MojoBeginReadData(consumer_, elements, num_bytes, flags); + } + + MojoResult EndReadData(uint32_t num_bytes_read) { + return MojoEndReadData(consumer_, num_bytes_read); + } + + MojoResult BeginWriteData(void** elements, + uint32_t* num_bytes, + bool all_or_none = false) { + MojoReadDataFlags flags = MOJO_WRITE_DATA_FLAG_NONE; + if (all_or_none) + flags |= MOJO_WRITE_DATA_FLAG_ALL_OR_NONE; + return MojoBeginWriteData(producer_, elements, num_bytes, flags); + } + + MojoResult EndWriteData(uint32_t num_bytes_written) { + return MojoEndWriteData(producer_, num_bytes_written); + } + + MojoResult CloseProducer() { + MojoResult rv = MojoClose(producer_); + producer_ = MOJO_HANDLE_INVALID; + return rv; + } + + MojoResult CloseConsumer() { + MojoResult rv = MojoClose(consumer_); + consumer_ = MOJO_HANDLE_INVALID; + return rv; + } + + MojoHandle producer_, consumer_; + + private: + DISALLOW_COPY_AND_ASSIGN(DataPipeTest); +}; + +TEST_F(DataPipeTest, Basic) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + + // We can write to a data pipe handle immediately. + int32_t elements[10] = {}; + uint32_t num_bytes = 0; + + num_bytes = + static_cast(arraysize(elements) * sizeof(elements[0])); + + elements[0] = 123; + elements[1] = 456; + num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(&elements[0], &num_bytes)); + + // Now wait for the other side to become readable. + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfied_signals); + + elements[0] = -1; + elements[1] = -1; + ASSERT_EQ(MOJO_RESULT_OK, ReadData(&elements[0], &num_bytes)); + ASSERT_EQ(static_cast(2u * sizeof(elements[0])), num_bytes); + ASSERT_EQ(elements[0], 123); + ASSERT_EQ(elements[1], 456); +} + +// Tests creation of data pipes with various (valid) options. +TEST_F(DataPipeTest, CreateAndMaybeTransfer) { + MojoCreateDataPipeOptions test_options[] = { + // Default options. + {}, + // Trivial element size, non-default capacity. + {kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1, // |element_num_bytes|. + 1000}, // |capacity_num_bytes|. + // Nontrivial element size, non-default capacity. + {kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 4, // |element_num_bytes|. + 4000}, // |capacity_num_bytes|. + // Nontrivial element size, default capacity. + {kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 100, // |element_num_bytes|. + 0} // |capacity_num_bytes|. + }; + for (size_t i = 0; i < arraysize(test_options); i++) { + MojoHandle producer_handle, consumer_handle; + MojoCreateDataPipeOptions* options = + i ? &test_options[i] : nullptr; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateDataPipe(options, &producer_handle, &consumer_handle)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(producer_handle)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(consumer_handle)); + } +} + +TEST_F(DataPipeTest, SimpleReadWrite) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + int32_t elements[10] = {}; + uint32_t num_bytes = 0; + + // Try reading; nothing there yet. + num_bytes = + static_cast(arraysize(elements) * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadData(elements, &num_bytes)); + + // Query; nothing there yet. + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Discard; nothing there yet. + num_bytes = static_cast(5u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, DiscardData(&num_bytes)); + + // Read with invalid |num_bytes|. + num_bytes = sizeof(elements[0]) + 1; + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, ReadData(elements, &num_bytes)); + + // Write two elements. + elements[0] = 123; + elements[1] = 456; + num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes)); + // It should have written everything (even without "all or none"). + ASSERT_EQ(2u * sizeof(elements[0]), num_bytes); + + // Wait. + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Query. + // TODO(vtl): It's theoretically possible (though not with the current + // implementation/configured limits) that not all the data has arrived yet. + // (The theoretically-correct assertion here is that |num_bytes| is |1 * ...| + // or |2 * ...|.) + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(2 * sizeof(elements[0]), num_bytes); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes)); + ASSERT_EQ(1u * sizeof(elements[0]), num_bytes); + ASSERT_EQ(123, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Query. + // TODO(vtl): See previous TODO. (If we got 2 elements there, however, we + // should get 1 here.) + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1 * sizeof(elements[0]), num_bytes); + + // Peek one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, false, true)); + ASSERT_EQ(1u * sizeof(elements[0]), num_bytes); + ASSERT_EQ(456, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Query. Still has 1 element remaining. + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1 * sizeof(elements[0]), num_bytes); + + // Try to read two elements, with "all or none". + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, + ReadData(elements, &num_bytes, true, false)); + ASSERT_EQ(-1, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Try to read two elements, without "all or none". + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, false, false)); + ASSERT_EQ(1u * sizeof(elements[0]), num_bytes); + ASSERT_EQ(456, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Query. + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); +} + +// Note: The "basic" waiting tests test that the "wait states" are correct in +// various situations; they don't test that waiters are properly awoken on state +// changes. (For that, we need to use multiple threads.) +TEST_F(DataPipeTest, BasicProducerWaiting) { + // Note: We take advantage of the fact that current for current + // implementations capacities are strict maximums. This is not guaranteed by + // the API. + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 2 * sizeof(int32_t) // |capacity_num_bytes|. + }; + Create(&options); + MojoHandleSignalsState hss; + + // Never readable. Already writable. + hss = GetSignalsState(producer_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Write two elements. + int32_t elements[2] = {123, 456}; + uint32_t num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + ASSERT_EQ(static_cast(2u * sizeof(elements[0])), num_bytes); + + // Wait for data to become available to the consumer. + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Peek one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, true)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + ASSERT_EQ(123, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, false)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + ASSERT_EQ(123, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Try writing, using a two-phase write. + void* buffer = nullptr; + num_bytes = static_cast(3u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&buffer, &num_bytes)); + EXPECT_TRUE(buffer); + ASSERT_GE(num_bytes, static_cast(1u * sizeof(elements[0]))); + + static_cast(buffer)[0] = 789; + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(static_cast( + 1u * sizeof(elements[0])))); + + // Read one element, using a two-phase read. + const void* read_buffer = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginReadData(&read_buffer, &num_bytes, false)); + EXPECT_TRUE(read_buffer); + // The two-phase read should be able to read at least one element. + ASSERT_GE(num_bytes, static_cast(1u * sizeof(elements[0]))); + ASSERT_EQ(456, static_cast(read_buffer)[0]); + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(static_cast( + 1u * sizeof(elements[0])))); + + // Write one element. + elements[0] = 123; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + + // Close the consumer. + CloseConsumer(); + + // It should now be never-writable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +TEST_F(DataPipeTest, PeerClosedProducerWaiting) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 2 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Close the consumer. + CloseConsumer(); + + // It should be signaled. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +TEST_F(DataPipeTest, PeerClosedConsumerWaiting) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 2 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Close the producer. + CloseProducer(); + + // It should be signaled. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +TEST_F(DataPipeTest, BasicConsumerWaiting) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Never writable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); + EXPECT_EQ(0u, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Write two elements. + int32_t elements[2] = {123, 456}; + uint32_t num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + + // Wait for readability. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Discard one element. + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, DiscardData(&num_bytes, true)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + + // Should still be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Peek one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, true)); + ASSERT_EQ(456, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Should still be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + ASSERT_EQ(456, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Write one element. + elements[0] = 789; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + + // Waiting should now succeed. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Close the producer. + CloseProducer(); + + // Should still be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE(hss.satisfied_signals & (MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Wait for the peer closed signal. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + ASSERT_EQ(789, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Should be never-readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +TEST_F(DataPipeTest, ConsumerNewDataReadable) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + EXPECT_EQ(MOJO_RESULT_OK, Create(&options)); + + int32_t elements[2] = {123, 456}; + uint32_t num_bytes = static_cast(2u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + + // The consumer handle should appear to be readable and have new data. + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE); + + // Now try to read a minimum of 6 elements. + int32_t read_elements[6]; + uint32_t num_read_bytes = sizeof(read_elements); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + MojoReadData(consumer_, read_elements, &num_read_bytes, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // The consumer should still appear to be readable but not with new data. + EXPECT_TRUE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE); + + // Write four more elements. + EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + + // The consumer handle should once again appear to be readable. + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE)); + + // Try again to read a minimum of 6 elements. Should succeed this time. + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadData(consumer_, read_elements, &num_read_bytes, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // And now the consumer is unreadable. + EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE); +} + +// Test with two-phase APIs and also closing the producer with an active +// consumer waiter. +TEST_F(DataPipeTest, ConsumerWaitingTwoPhase) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write two elements. + int32_t* elements = nullptr; + void* buffer = nullptr; + // Request room for three (but we'll only write two). + uint32_t num_bytes = static_cast(3u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&buffer, &num_bytes, false)); + EXPECT_TRUE(buffer); + EXPECT_GE(num_bytes, static_cast(3u * sizeof(elements[0]))); + elements = static_cast(buffer); + elements[0] = 123; + elements[1] = 456; + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(2u * sizeof(elements[0]))); + + // Wait for readability. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read one element. + // Request two in all-or-none mode, but only read one. + const void* read_buffer = nullptr; + num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes, true)); + EXPECT_TRUE(read_buffer); + ASSERT_EQ(static_cast(2u * sizeof(elements[0])), num_bytes); + const int32_t* read_elements = static_cast(read_buffer); + ASSERT_EQ(123, read_elements[0]); + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(1u * sizeof(elements[0]))); + + // Should still be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read one element. + // Request three, but not in all-or-none mode. + read_buffer = nullptr; + num_bytes = static_cast(3u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes)); + EXPECT_TRUE(read_buffer); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + read_elements = static_cast(read_buffer); + ASSERT_EQ(456, read_elements[0]); + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(1u * sizeof(elements[0]))); + + // Close the producer. + CloseProducer(); + + // Should be never-readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +// Tests that data pipes aren't writable/readable during two-phase writes/reads. +TEST_F(DataPipeTest, BasicTwoPhaseWaiting) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // It should be writable. + hss = GetSignalsState(producer_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + uint32_t num_bytes = static_cast(1u * sizeof(int32_t)); + void* write_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes)); + EXPECT_TRUE(write_ptr); + EXPECT_GE(num_bytes, static_cast(1u * sizeof(int32_t))); + + // At this point, it shouldn't be writable. + hss = GetSignalsState(producer_); + ASSERT_EQ(0u, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // It shouldn't be readable yet either (we'll wait later). + hss = GetSignalsState(consumer_); + ASSERT_EQ(0u, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + static_cast(write_ptr)[0] = 123; + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(1u * sizeof(int32_t))); + + // It should immediately be writable again. + hss = GetSignalsState(producer_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // It should become readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Start another two-phase write and check that it's readable even in the + // middle of it. + num_bytes = static_cast(1u * sizeof(int32_t)); + write_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes)); + EXPECT_TRUE(write_ptr); + EXPECT_GE(num_bytes, static_cast(1u * sizeof(int32_t))); + + // It should be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // End the two-phase write without writing anything. + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(0u)); + + // Start a two-phase read. + num_bytes = static_cast(1u * sizeof(int32_t)); + const void* read_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_ptr, &num_bytes)); + EXPECT_TRUE(read_ptr); + ASSERT_EQ(static_cast(1u * sizeof(int32_t)), num_bytes); + + // At this point, it should still be writable. + hss = GetSignalsState(producer_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // But not readable. + hss = GetSignalsState(consumer_); + ASSERT_EQ(0u, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // End the two-phase read without reading anything. + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(0u)); + + // It should be readable again. + hss = GetSignalsState(consumer_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); +} + +void Seq(int32_t start, size_t count, int32_t* out) { + for (size_t i = 0; i < count; i++) + out[i] = start + static_cast(i); +} + +TEST_F(DataPipeTest, AllOrNone) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 10 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Try writing more than the total capacity of the pipe. + uint32_t num_bytes = 20u * sizeof(int32_t); + int32_t buffer[100]; + Seq(0, arraysize(buffer), buffer); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, WriteData(buffer, &num_bytes, true)); + + // Should still be empty. + num_bytes = ~0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Write some data. + num_bytes = 5u * sizeof(int32_t); + Seq(100, arraysize(buffer), buffer); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(buffer, &num_bytes, true)); + ASSERT_EQ(5u * sizeof(int32_t), num_bytes); + + // Wait for data. + // TODO(vtl): There's no real guarantee that all the data will become + // available at once (except that in current implementations, with reasonable + // limits, it will). Eventually, we'll be able to wait for a specified amount + // of data to become available. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Half full. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(5u * sizeof(int32_t), num_bytes); + + // Try writing more than the available capacity of the pipe, but less than the + // total capacity. + num_bytes = 6u * sizeof(int32_t); + Seq(200, arraysize(buffer), buffer); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, WriteData(buffer, &num_bytes, true)); + + // Try reading too much. + num_bytes = 11u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, ReadData(buffer, &num_bytes, true)); + int32_t expected_buffer[100]; + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try discarding too much. + num_bytes = 11u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, DiscardData(&num_bytes, true)); + + // Just a little. + num_bytes = 2u * sizeof(int32_t); + Seq(300, arraysize(buffer), buffer); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(buffer, &num_bytes, true)); + ASSERT_EQ(2u * sizeof(int32_t), num_bytes); + + // Just right. + num_bytes = 3u * sizeof(int32_t); + Seq(400, arraysize(buffer), buffer); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(buffer, &num_bytes, true)); + ASSERT_EQ(3u * sizeof(int32_t), num_bytes); + + // TODO(vtl): Hack (see also the TODO above): We can't currently wait for a + // specified amount of data to be available, so poll. + for (size_t i = 0; i < kMaxPoll; i++) { + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + if (num_bytes >= 10u * sizeof(int32_t)) + break; + + test::Sleep(test::EpsilonDeadline()); + } + ASSERT_EQ(10u * sizeof(int32_t), num_bytes); + + // Read half. + num_bytes = 5u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes, true)); + ASSERT_EQ(5u * sizeof(int32_t), num_bytes); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + Seq(100, 5, expected_buffer); + ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try reading too much again. + num_bytes = 6u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, ReadData(buffer, &num_bytes, true)); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try discarding too much again. + num_bytes = 6u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, DiscardData(&num_bytes, true)); + + // Discard a little. + num_bytes = 2u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_OK, DiscardData(&num_bytes, true)); + ASSERT_EQ(2u * sizeof(int32_t), num_bytes); + + // Three left. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(3u * sizeof(int32_t), num_bytes); + + // Close the producer, then test producer-closed cases. + CloseProducer(); + + // Wait. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Try reading too much; "failed precondition" since the producer is closed. + num_bytes = 4u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + ReadData(buffer, &num_bytes, true)); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try discarding too much; "failed precondition" again. + num_bytes = 4u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, DiscardData(&num_bytes, true)); + + // Read a little. + num_bytes = 2u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes, true)); + ASSERT_EQ(2u * sizeof(int32_t), num_bytes); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + Seq(400, 2, expected_buffer); + ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Discard the remaining element. + num_bytes = 1u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_OK, DiscardData(&num_bytes, true)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); + + // Empty again. + num_bytes = ~0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); +} + +// Tests that |ProducerWriteData()| and |ConsumerReadData()| writes and reads, +// respectively, as much as possible, even if it may have to "wrap around" the +// internal circular buffer. (Note that the two-phase write and read need not do +// this.) +TEST_F(DataPipeTest, WrapAround) { + unsigned char test_data[1000]; + for (size_t i = 0; i < arraysize(test_data); i++) + test_data[i] = static_cast(i); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 100u // |capacity_num_bytes|. + }; + + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write 20 bytes. + uint32_t num_bytes = 20u; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(&test_data[0], &num_bytes, true)); + ASSERT_EQ(20u, num_bytes); + + // Wait for data. + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read 10 bytes. + unsigned char read_buffer[1000] = {0}; + num_bytes = 10u; + ASSERT_EQ(MOJO_RESULT_OK, ReadData(read_buffer, &num_bytes, true)); + ASSERT_EQ(10u, num_bytes); + ASSERT_EQ(0, memcmp(read_buffer, &test_data[0], 10u)); + + // Check that a two-phase write can now only write (at most) 80 bytes. (This + // checks an implementation detail; this behavior is not guaranteed.) + void* write_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginWriteData(&write_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(write_buffer_ptr); + ASSERT_EQ(80u, num_bytes); + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(0)); + + size_t total_num_bytes = 0; + while (total_num_bytes < 90) { + // Wait to write. + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); + ASSERT_EQ(hss.satisfied_signals, MOJO_HANDLE_SIGNAL_WRITABLE); + ASSERT_EQ(hss.satisfiable_signals, + MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + // Write as much as we can. + num_bytes = 100; + ASSERT_EQ(MOJO_RESULT_OK, + WriteData(&test_data[20 + total_num_bytes], &num_bytes, false)); + total_num_bytes += num_bytes; + } + + ASSERT_EQ(90u, total_num_bytes); + + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(100u, num_bytes); + + // Check that a two-phase read can now only read (at most) 90 bytes. (This + // checks an implementation detail; this behavior is not guaranteed.) + const void* read_buffer_ptr = nullptr; + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(read_buffer_ptr); + ASSERT_EQ(90u, num_bytes); + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(0)); + + // Read as much as possible. We should read 100 bytes. + num_bytes = static_cast(arraysize(read_buffer) * + sizeof(read_buffer[0])); + memset(read_buffer, 0, num_bytes); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(read_buffer, &num_bytes)); + ASSERT_EQ(100u, num_bytes); + ASSERT_EQ(0, memcmp(read_buffer, &test_data[10], 100u)); +} + +// Tests the behavior of writing (simple and two-phase), closing the producer, +// then reading (simple and two-phase). +TEST_F(DataPipeTest, WriteCloseProducerRead) { + const char kTestData[] = "hello world"; + const uint32_t kTestDataSize = static_cast(sizeof(kTestData)); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + + // Write some data, so we'll have something to read. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes, false)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Write it again, so we'll have something left over. + num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes, false)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Start two-phase write. + void* write_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginWriteData(&write_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(write_buffer_ptr); + EXPECT_GT(num_bytes, 0u); + + // TODO(vtl): (See corresponding TODO in TwoPhaseAllOrNone.) + for (size_t i = 0; i < kMaxPoll; i++) { + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + if (num_bytes >= 2u * kTestDataSize) + break; + + test::Sleep(test::EpsilonDeadline()); + } + ASSERT_EQ(2u * kTestDataSize, num_bytes); + + // Start two-phase read. + const void* read_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginReadData(&read_buffer_ptr, &num_bytes)); + EXPECT_TRUE(read_buffer_ptr); + ASSERT_EQ(2u * kTestDataSize, num_bytes); + + // Close the producer. + CloseProducer(); + + // The consumer can finish its two-phase read. + ASSERT_EQ(0, memcmp(read_buffer_ptr, kTestData, kTestDataSize)); + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(kTestDataSize)); + + // And start another. + read_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginReadData(&read_buffer_ptr, &num_bytes)); + EXPECT_TRUE(read_buffer_ptr); + ASSERT_EQ(kTestDataSize, num_bytes); +} + + +// Tests the behavior of interrupting a two-phase read and write by closing the +// consumer. +TEST_F(DataPipeTest, TwoPhaseWriteReadCloseConsumer) { + const char kTestData[] = "hello world"; + const uint32_t kTestDataSize = static_cast(sizeof(kTestData)); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write some data, so we'll have something to read. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Start two-phase write. + void* write_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_buffer_ptr, &num_bytes)); + EXPECT_TRUE(write_buffer_ptr); + ASSERT_GT(num_bytes, kTestDataSize); + + // Wait for data. + // TODO(vtl): (See corresponding TODO in AllOrNone.) + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Start two-phase read. + const void* read_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer_ptr, &num_bytes)); + EXPECT_TRUE(read_buffer_ptr); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Close the consumer. + CloseConsumer(); + + // Wait for producer to know that the consumer is closed. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); + + // Actually write some data. (Note: Premature freeing of the buffer would + // probably only be detected under ASAN or similar.) + memcpy(write_buffer_ptr, kTestData, kTestDataSize); + // Note: Even though the consumer has been closed, ending the two-phase + // write will report success. + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(kTestDataSize)); + + // But trying to write should result in failure. + num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, WriteData(kTestData, &num_bytes)); + + // As will trying to start another two-phase write. + write_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + BeginWriteData(&write_buffer_ptr, &num_bytes)); +} + +// Tests the behavior of "interrupting" a two-phase write by closing both the +// producer and the consumer. +TEST_F(DataPipeTest, TwoPhaseWriteCloseBoth) { + const uint32_t kTestDataSize = 15u; + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + + // Start two-phase write. + void* write_buffer_ptr = nullptr; + uint32_t num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_buffer_ptr, &num_bytes)); + EXPECT_TRUE(write_buffer_ptr); + ASSERT_GT(num_bytes, kTestDataSize); +} + +// Tests the behavior of writing, closing the producer, and then reading (with +// and without data remaining). +TEST_F(DataPipeTest, WriteCloseProducerReadNoData) { + const char kTestData[] = "hello world"; + const uint32_t kTestDataSize = static_cast(sizeof(kTestData)); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write some data, so we'll have something to read. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Close the producer. + CloseProducer(); + + // Wait. (Note that once the consumer knows that the producer is closed, it + // must also know about all the data that was sent.) + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Peek that data. + char buffer[1000]; + num_bytes = static_cast(sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes, false, true)); + ASSERT_EQ(kTestDataSize, num_bytes); + ASSERT_EQ(0, memcmp(buffer, kTestData, kTestDataSize)); + + // Read that data. + memset(buffer, 0, 1000); + num_bytes = static_cast(sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes)); + ASSERT_EQ(kTestDataSize, num_bytes); + ASSERT_EQ(0, memcmp(buffer, kTestData, kTestDataSize)); + + // A second read should fail. + num_bytes = static_cast(sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ReadData(buffer, &num_bytes)); + + // A two-phase read should also fail. + const void* read_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + BeginReadData(&read_buffer_ptr, &num_bytes)); + + // Ditto for discard. + num_bytes = 10u; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, DiscardData(&num_bytes)); +} + +// Test that during a two phase read the memory stays valid even if more data +// comes in. +TEST_F(DataPipeTest, TwoPhaseReadMemoryStable) { + const char kTestData[] = "hello world"; + const uint32_t kTestDataSize = static_cast(sizeof(kTestData)); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write some data. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Wait for the data. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Begin a two-phase read. + const void* read_buffer_ptr = nullptr; + uint32_t read_buffer_size = 0u; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer_ptr, &read_buffer_size)); + + // Write more data. + const char kExtraData[] = "bye world"; + const uint32_t kExtraDataSize = static_cast(sizeof(kExtraData)); + num_bytes = kExtraDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kExtraData, &num_bytes)); + ASSERT_EQ(kExtraDataSize, num_bytes); + + // Close the producer. + CloseProducer(); + + // Wait. (Note that once the consumer knows that the producer is closed, it + // must also have received the extra data). + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read the two phase memory to check it's still valid. + ASSERT_EQ(0, memcmp(read_buffer_ptr, kTestData, kTestDataSize)); + EndReadData(read_buffer_size); +} + +// Test that two-phase reads/writes behave correctly when given invalid +// arguments. +TEST_F(DataPipeTest, TwoPhaseMoreInvalidArguments) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 10 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // No data. + uint32_t num_bytes = 1000u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Try "ending" a two-phase write when one isn't active. + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + EndWriteData(1u * sizeof(int32_t))); + + // Wait a bit, to make sure that if a signal were (incorrectly) sent, it'd + // have time to propagate. + test::Sleep(test::EpsilonDeadline()); + + // Still no data. + num_bytes = 1000u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Try ending a two-phase write with an invalid amount (too much). + num_bytes = 0u; + void* write_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes)); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + EndWriteData(num_bytes + static_cast(sizeof(int32_t)))); + + // But the two-phase write still ended. + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, EndWriteData(0u)); + + // Wait a bit (as above). + test::Sleep(test::EpsilonDeadline()); + + // Still no data. + num_bytes = 1000u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Try ending a two-phase write with an invalid amount (not a multiple of the + // element size). + num_bytes = 0u; + write_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes)); + EXPECT_GE(num_bytes, 1u); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, EndWriteData(1u)); + + // But the two-phase write still ended. + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, EndWriteData(0u)); + + // Wait a bit (as above). + test::Sleep(test::EpsilonDeadline()); + + // Still no data. + num_bytes = 1000u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Now write some data, so we'll be able to try reading. + int32_t element = 123; + num_bytes = 1u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(&element, &num_bytes)); + + // Wait for data. + // TODO(vtl): (See corresponding TODO in AllOrNone.) + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // One element available. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); + + // Try "ending" a two-phase read when one isn't active. + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, EndReadData(1u * sizeof(int32_t))); + + // Still one element available. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); + + // Try ending a two-phase read with an invalid amount (too much). + num_bytes = 0u; + const void* read_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_ptr, &num_bytes)); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + EndReadData(num_bytes + static_cast(sizeof(int32_t)))); + + // Still one element available. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); + + // Try ending a two-phase read with an invalid amount (not a multiple of the + // element size). + num_bytes = 0u; + read_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_ptr, &num_bytes)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); + ASSERT_EQ(123, static_cast(read_ptr)[0]); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, EndReadData(1u)); + + // Still one element available. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); +} + +// Test that a producer can be sent over a MP. +TEST_F(DataPipeTest, SendProducer) { + const char kTestData[] = "hello world"; + const uint32_t kTestDataSize = static_cast(sizeof(kTestData)); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write some data. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Wait for the data. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Check the data. + const void* read_buffer = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginReadData(&read_buffer, &num_bytes, false)); + ASSERT_EQ(0, memcmp(read_buffer, kTestData, kTestDataSize)); + EndReadData(num_bytes); + + // Now send the producer over a MP so that it's serialized. + MojoHandle pipe0, pipe1; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateMessagePipe(nullptr, &pipe0, &pipe1)); + + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(pipe0, nullptr, 0, &producer_, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + producer_ = MOJO_HANDLE_INVALID; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + uint32_t num_handles = 1; + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(pipe1, nullptr, 0, &producer_, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(num_handles, 1u); + + // Write more data. + const char kExtraData[] = "bye world"; + const uint32_t kExtraDataSize = static_cast(sizeof(kExtraData)); + num_bytes = kExtraDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kExtraData, &num_bytes)); + ASSERT_EQ(kExtraDataSize, num_bytes); + + // Wait for it. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Check the second write. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginReadData(&read_buffer, &num_bytes, false)); + ASSERT_EQ(0, memcmp(read_buffer, kExtraData, kExtraDataSize)); + EndReadData(num_bytes); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipe0)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipe1)); +} + +// Ensures that if a data pipe consumer whose producer has closed is passed over +// a message pipe, the deserialized dispatcher is also marked as having a closed +// peer. +TEST_F(DataPipeTest, ConsumerWithClosedProducerSent) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + + // We can write to a data pipe handle immediately. + int32_t data = 123; + uint32_t num_bytes = sizeof(data); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(&data, &num_bytes)); + ASSERT_EQ(MOJO_RESULT_OK, CloseProducer()); + + // Now wait for the other side to become readable and to see the peer closed. + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfiable_signals); + + // Now send the consumer over a MP so that it's serialized. + MojoHandle pipe0, pipe1; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateMessagePipe(nullptr, &pipe0, &pipe1)); + + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(pipe0, nullptr, 0, &consumer_, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + consumer_ = MOJO_HANDLE_INVALID; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1, MOJO_HANDLE_SIGNAL_READABLE, &state)); + uint32_t num_handles = 1; + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(pipe1, nullptr, 0, &consumer_, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(num_handles, 1u); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfiable_signals); + + int32_t read_data; + ASSERT_EQ(MOJO_RESULT_OK, ReadData(&read_data, &num_bytes)); + ASSERT_EQ(sizeof(read_data), num_bytes); + ASSERT_EQ(data, read_data); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipe0)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipe1)); +} + +bool WriteAllData(MojoHandle producer, + const void* elements, + uint32_t num_bytes) { + for (size_t i = 0; i < kMaxPoll; i++) { + // Write as much data as we can. + uint32_t write_bytes = num_bytes; + MojoResult result = MojoWriteData(producer, elements, &write_bytes, + MOJO_WRITE_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + num_bytes -= write_bytes; + elements = static_cast(elements) + write_bytes; + if (num_bytes == 0) + return true; + } else { + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, result); + } + + MojoHandleSignalsState hss = MojoHandleSignalsState(); + EXPECT_EQ(MOJO_RESULT_OK, test::MojoTestBase::WaitForSignals( + producer, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + } + + return false; +} + +// If |expect_empty| is true, expect |consumer| to be empty after reading. +bool ReadAllData(MojoHandle consumer, + void* elements, + uint32_t num_bytes, + bool expect_empty) { + for (size_t i = 0; i < kMaxPoll; i++) { + // Read as much data as we can. + uint32_t read_bytes = num_bytes; + MojoResult result = + MojoReadData(consumer, elements, &read_bytes, MOJO_READ_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + num_bytes -= read_bytes; + elements = static_cast(elements) + read_bytes; + if (num_bytes == 0) { + if (expect_empty) { + // Expect no more data. + test::Sleep(test::TinyDeadline()); + MojoReadData(consumer, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_QUERY); + EXPECT_EQ(0u, num_bytes); + } + return true; + } + } else { + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, result); + } + + MojoHandleSignalsState hss = MojoHandleSignalsState(); + EXPECT_EQ(MOJO_RESULT_OK, test::MojoTestBase::WaitForSignals( + consumer, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + // Peer could have become closed while we're still waiting for data. + EXPECT_TRUE(MOJO_HANDLE_SIGNAL_READABLE & hss.satisfied_signals); + EXPECT_TRUE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_TRUE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_CLOSED); + } + + return num_bytes == 0; +} + +#if !defined(OS_IOS) + +TEST_F(DataPipeTest, Multiprocess) { + const uint32_t kTestDataSize = + static_cast(sizeof(kMultiprocessTestData)); + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1, // |element_num_bytes|. + kMultiprocessCapacity // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + + RUN_CHILD_ON_PIPE(MultiprocessClient, server_mp) + // Send some data before serialising and sending the data pipe over. + // This is the first write so we don't need to use WriteAllData. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kMultiprocessTestData, &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Send child process the data pipe. + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(server_mp, nullptr, 0, &consumer_, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Send a bunch of data of varying sizes. + uint8_t buffer[100]; + int seq = 0; + for (int i = 0; i < kMultiprocessMaxIter; ++i) { + for (uint32_t size = 1; size <= kMultiprocessCapacity; size++) { + for (unsigned int j = 0; j < size; ++j) + buffer[j] = seq + j; + EXPECT_TRUE(WriteAllData(producer_, buffer, size)); + seq += size; + } + } + + // Write the test string in again. + ASSERT_TRUE(WriteAllData(producer_, kMultiprocessTestData, kTestDataSize)); + + // Swap ends. + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(server_mp, nullptr, 0, &producer_, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Receive the consumer from the other side. + producer_ = MOJO_HANDLE_INVALID; + MojoHandleSignalsState hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + MojoHandle handles[2]; + uint32_t num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(server_mp, nullptr, 0, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, num_handles); + consumer_ = handles[0]; + + // Read the test string twice. Once for when we sent it, and once for the + // other end sending it. + for (int i = 0; i < 2; ++i) { + EXPECT_TRUE(ReadAllData(consumer_, buffer, kTestDataSize, i == 1)); + EXPECT_EQ(0, memcmp(buffer, kMultiprocessTestData, kTestDataSize)); + } + + WriteMessage(server_mp, "quit"); + + // Don't have to close the consumer here because it will be done for us. + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessClient, DataPipeTest, client_mp) { + const uint32_t kTestDataSize = + static_cast(sizeof(kMultiprocessTestData)); + + // Receive the data pipe from the other side. + MojoHandle consumer = MOJO_HANDLE_INVALID; + MojoHandleSignalsState hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + MojoHandle handles[2]; + uint32_t num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(client_mp, nullptr, 0, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, num_handles); + consumer = handles[0]; + + // Read the initial string that was sent. + int32_t buffer[100]; + EXPECT_TRUE(ReadAllData(consumer, buffer, kTestDataSize, false)); + EXPECT_EQ(0, memcmp(buffer, kMultiprocessTestData, kTestDataSize)); + + // Receive the main data and check it is correct. + int seq = 0; + uint8_t expected_buffer[100]; + for (int i = 0; i < kMultiprocessMaxIter; ++i) { + for (uint32_t size = 1; size <= kMultiprocessCapacity; ++size) { + for (unsigned int j = 0; j < size; ++j) + expected_buffer[j] = seq + j; + EXPECT_TRUE(ReadAllData(consumer, buffer, size, false)); + EXPECT_EQ(0, memcmp(buffer, expected_buffer, size)); + + seq += size; + } + } + + // Swap ends. + ASSERT_EQ(MOJO_RESULT_OK, MojoWriteMessage(client_mp, nullptr, 0, &consumer, + 1, MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Receive the producer from the other side. + MojoHandle producer = MOJO_HANDLE_INVALID; + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(client_mp, nullptr, 0, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, num_handles); + producer = handles[0]; + + // Write the test string one more time. + EXPECT_TRUE(WriteAllData(producer, kMultiprocessTestData, kTestDataSize)); + + // We swapped ends, so close the producer. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + + // Wait to receive a "quit" message before exiting. + EXPECT_EQ("quit", ReadMessage(client_mp)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(WriteAndCloseProducer, DataPipeTest, h) { + MojoHandle p; + std::string message = ReadMessageWithHandles(h, &p, 1); + + // Write some data to the producer and close it. + uint32_t num_bytes = static_cast(message.size()); + EXPECT_EQ(MOJO_RESULT_OK, MojoWriteData(p, message.data(), &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(num_bytes, static_cast(message.size())); + + // Close the producer before quitting. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(p)); + + // Wait for a quit message. + EXPECT_EQ("quit", ReadMessage(h)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReadAndCloseConsumer, DataPipeTest, h) { + MojoHandle c; + std::string expected_message = ReadMessageWithHandles(h, &c, 1); + + // Wait for the consumer to become readable. + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE)); + + // Drain the consumer and expect to find the given message. + uint32_t num_bytes = static_cast(expected_message.size()); + std::vector bytes(expected_message.size()); + EXPECT_EQ(MOJO_RESULT_OK, MojoReadData(c, bytes.data(), &num_bytes, + MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(num_bytes, static_cast(bytes.size())); + + std::string message(bytes.data(), bytes.size()); + EXPECT_EQ(expected_message, message); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + + // Wait for a quit message. + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_F(DataPipeTest, SendConsumerAndCloseProducer) { + // Create a new data pipe. + MojoHandle p, c; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(nullptr, &p ,&c)); + + RUN_CHILD_ON_PIPE(WriteAndCloseProducer, producer_client) + RUN_CHILD_ON_PIPE(ReadAndCloseConsumer, consumer_client) + const std::string kMessage = "Hello, world!"; + WriteMessageWithHandles(producer_client, kMessage, &p, 1); + WriteMessageWithHandles(consumer_client, kMessage, &c, 1); + + WriteMessage(consumer_client, "quit"); + END_CHILD() + + WriteMessage(producer_client, "quit"); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateAndWrite, DataPipeTest, h) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1, // |element_num_bytes|. + kMultiprocessCapacity // |capacity_num_bytes|. + }; + + MojoHandle p, c; + ASSERT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(&options, &p, &c)); + + const std::string kMessage = "Hello, world!"; + WriteMessageWithHandles(h, kMessage, &c, 1); + + // Write some data to the producer and close it. + uint32_t num_bytes = static_cast(kMessage.size()); + EXPECT_EQ(MOJO_RESULT_OK, MojoWriteData(p, kMessage.data(), &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(num_bytes, static_cast(kMessage.size())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(p)); + + // Wait for a quit message. + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_F(DataPipeTest, CreateInChild) { + RUN_CHILD_ON_PIPE(CreateAndWrite, child) + MojoHandle c; + std::string expected_message = ReadMessageWithHandles(child, &c, 1); + + // Wait for the consumer to become readable. + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE)); + + // Drain the consumer and expect to find the given message. + uint32_t num_bytes = static_cast(expected_message.size()); + std::vector bytes(expected_message.size()); + EXPECT_EQ(MOJO_RESULT_OK, MojoReadData(c, bytes.data(), &num_bytes, + MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(num_bytes, static_cast(bytes.size())); + + std::string message(bytes.data(), bytes.size()); + EXPECT_EQ(expected_message, message); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + WriteMessage(child, "quit"); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(DataPipeStatusChangeInTransitClient, + DataPipeTest, parent) { + // This test verifies that peer closure is detectable through various + // mechanisms when it races with handle transfer. + + MojoHandle handles[6]; + EXPECT_EQ("o_O", ReadMessageWithHandles(parent, handles, 6)); + MojoHandle* producers = &handles[0]; + MojoHandle* consumers = &handles[3]; + + // Wait on producer 0 + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(producers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + // Wait on consumer 0 + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + base::MessageLoop message_loop; + + // Wait on producer 1 and consumer 1 using SimpleWatchers. + { + base::RunLoop run_loop; + int count = 0; + auto callback = base::Bind( + [] (base::RunLoop* loop, int* count, MojoResult result) { + EXPECT_EQ(MOJO_RESULT_OK, result); + if (++*count == 2) + loop->Quit(); + }, + &run_loop, &count); + SimpleWatcher producer_watcher(FROM_HERE, + SimpleWatcher::ArmingPolicy::AUTOMATIC); + SimpleWatcher consumer_watcher(FROM_HERE, + SimpleWatcher::ArmingPolicy::AUTOMATIC); + producer_watcher.Watch(Handle(producers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, + callback); + consumer_watcher.Watch(Handle(consumers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, + callback); + run_loop.Run(); + EXPECT_EQ(2, count); + } + + // Wait on producer 2 by polling with MojoWriteData. + MojoResult result; + do { + uint32_t num_bytes = 0; + result = MojoWriteData( + producers[2], nullptr, &num_bytes, MOJO_WRITE_DATA_FLAG_NONE); + } while (result == MOJO_RESULT_OK); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + + // Wait on consumer 2 by polling with MojoReadData. + do { + char byte; + uint32_t num_bytes = 1; + result = MojoReadData( + consumers[2], &byte, &num_bytes, MOJO_READ_DATA_FLAG_NONE); + } while (result == MOJO_RESULT_SHOULD_WAIT); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + + for (size_t i = 0; i < 6; ++i) + CloseHandle(handles[i]); +} + +TEST_F(DataPipeTest, StatusChangeInTransit) { + MojoHandle producers[6]; + MojoHandle consumers[6]; + for (size_t i = 0; i < 6; ++i) + CreateDataPipe(&producers[i], &consumers[i], 1); + + RUN_CHILD_ON_PIPE(DataPipeStatusChangeInTransitClient, child) + MojoHandle handles[] = { producers[0], producers[1], producers[2], + consumers[3], consumers[4], consumers[5] }; + + // Send 3 producers and 3 consumers, and let their transfer race with their + // peers' closure. + WriteMessageWithHandles(child, "o_O", handles, 6); + + for (size_t i = 0; i < 3; ++i) + CloseHandle(consumers[i]); + for (size_t i = 3; i < 6; ++i) + CloseHandle(producers[i]); + END_CHILD() +} + +#endif // !defined(OS_IOS) + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/dispatcher.cc b/mojo/edk/system/dispatcher.cc new file mode 100644 index 0000000000000000000000000000000000000000..7cdbe910d9b9dc0eac38320ac8b32eb0a7446f96 --- /dev/null +++ b/mojo/edk/system/dispatcher.cc @@ -0,0 +1,198 @@ +// Copyright 2013 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. + +#include "mojo/edk/system/dispatcher.h" + +#include "base/logging.h" +#include "mojo/edk/system/configuration.h" +#include "mojo/edk/system/data_pipe_consumer_dispatcher.h" +#include "mojo/edk/system/data_pipe_producer_dispatcher.h" +#include "mojo/edk/system/message_pipe_dispatcher.h" +#include "mojo/edk/system/platform_handle_dispatcher.h" +#include "mojo/edk/system/shared_buffer_dispatcher.h" + +namespace mojo { +namespace edk { + +Dispatcher::DispatcherInTransit::DispatcherInTransit() {} + +Dispatcher::DispatcherInTransit::DispatcherInTransit( + const DispatcherInTransit& other) = default; + +Dispatcher::DispatcherInTransit::~DispatcherInTransit() {} + +MojoResult Dispatcher::WatchDispatcher(scoped_refptr dispatcher, + MojoHandleSignals signals, + uintptr_t context) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::CancelWatch(uintptr_t context) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::Arm(uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::WriteMessage(std::unique_ptr message, + MojoWriteMessageFlags flags) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::ReadMessage(std::unique_ptr* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags, + bool read_any_size) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::DuplicateBufferHandle( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr* new_dispatcher) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::MapBuffer( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + std::unique_ptr* mapping) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::EndReadData(uint32_t num_bytes_read) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::WriteData(const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::BeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::EndWriteData(uint32_t num_bytes_written) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::AddWaitingDispatcher( + const scoped_refptr& dispatcher, + MojoHandleSignals signals, + uintptr_t context) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::RemoveWaitingDispatcher( + const scoped_refptr& dispatcher) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::GetReadyDispatchers(uint32_t* count, + DispatcherVector* dispatchers, + MojoResult* results, + uintptr_t* contexts) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +HandleSignalsState Dispatcher::GetHandleSignalsState() const { + return HandleSignalsState(); +} + +MojoResult Dispatcher::AddWatcherRef( + const scoped_refptr& watcher, + uintptr_t context) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +void Dispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_platform_handles) { + *num_bytes = 0; + *num_ports = 0; + *num_platform_handles = 0; +} + +bool Dispatcher::EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) { + LOG(ERROR) << "Attempting to serialize a non-transferrable dispatcher."; + return true; +} + +bool Dispatcher::BeginTransit() { return true; } + +void Dispatcher::CompleteTransitAndClose() {} + +void Dispatcher::CancelTransit() {} + +// static +scoped_refptr Dispatcher::Deserialize( + Type type, + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* platform_handles, + size_t num_platform_handles) { + switch (type) { + case Type::MESSAGE_PIPE: + return MessagePipeDispatcher::Deserialize( + bytes, num_bytes, ports, num_ports, platform_handles, + num_platform_handles); + case Type::SHARED_BUFFER: + return SharedBufferDispatcher::Deserialize( + bytes, num_bytes, ports, num_ports, platform_handles, + num_platform_handles); + case Type::DATA_PIPE_CONSUMER: + return DataPipeConsumerDispatcher::Deserialize( + bytes, num_bytes, ports, num_ports, platform_handles, + num_platform_handles); + case Type::DATA_PIPE_PRODUCER: + return DataPipeProducerDispatcher::Deserialize( + bytes, num_bytes, ports, num_ports, platform_handles, + num_platform_handles); + case Type::PLATFORM_HANDLE: + return PlatformHandleDispatcher::Deserialize( + bytes, num_bytes, ports, num_ports, platform_handles, + num_platform_handles); + default: + LOG(ERROR) << "Deserializing invalid dispatcher type."; + return nullptr; + } +} + +Dispatcher::Dispatcher() {} + +Dispatcher::~Dispatcher() {} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/dispatcher.h b/mojo/edk/system/dispatcher.h new file mode 100644 index 0000000000000000000000000000000000000000..db1f1f18d70facee3daac524b68d203484e384e8 --- /dev/null +++ b/mojo/edk/system/dispatcher.h @@ -0,0 +1,245 @@ +// Copyright 2013 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 MOJO_EDK_SYSTEM_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_DISPATCHER_H_ + +#include +#include + +#include +#include +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/ports/name.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/edk/system/watch.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { +namespace edk { + +class Dispatcher; +class MessageForTransit; + +using DispatcherVector = std::vector>; + +// A |Dispatcher| implements Mojo EDK calls that are associated with a +// particular MojoHandle. +class MOJO_SYSTEM_IMPL_EXPORT Dispatcher + : public base::RefCountedThreadSafe { + public: + struct DispatcherInTransit { + DispatcherInTransit(); + DispatcherInTransit(const DispatcherInTransit& other); + ~DispatcherInTransit(); + + scoped_refptr dispatcher; + MojoHandle local_handle; + }; + + enum class Type { + UNKNOWN = 0, + MESSAGE_PIPE, + DATA_PIPE_PRODUCER, + DATA_PIPE_CONSUMER, + SHARED_BUFFER, + WATCHER, + + // "Private" types (not exposed via the public interface): + PLATFORM_HANDLE = -1, + }; + + // All Dispatchers must minimally implement these methods. + + virtual Type GetType() const = 0; + virtual MojoResult Close() = 0; + + ///////////// Watcher API //////////////////// + + virtual MojoResult WatchDispatcher(scoped_refptr dispatcher, + MojoHandleSignals signals, + uintptr_t context); + virtual MojoResult CancelWatch(uintptr_t context); + virtual MojoResult Arm(uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states); + + ///////////// Message pipe API ///////////// + + virtual MojoResult WriteMessage(std::unique_ptr message, + MojoWriteMessageFlags flags); + + virtual MojoResult ReadMessage(std::unique_ptr* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags, + bool read_any_size); + + ///////////// Shared buffer API ///////////// + + // |options| may be null. |new_dispatcher| must not be null, but + // |*new_dispatcher| should be null (and will contain the dispatcher for the + // new handle on success). + virtual MojoResult DuplicateBufferHandle( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr* new_dispatcher); + + virtual MojoResult MapBuffer( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + std::unique_ptr* mapping); + + ///////////// Data pipe consumer API ///////////// + + virtual MojoResult ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags); + + virtual MojoResult BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags); + + virtual MojoResult EndReadData(uint32_t num_bytes_read); + + ///////////// Data pipe producer API ///////////// + + virtual MojoResult WriteData(const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags); + + virtual MojoResult BeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags); + + virtual MojoResult EndWriteData(uint32_t num_bytes_written); + + ///////////// Wait set API ///////////// + + // Adds a dispatcher to wait on. When the dispatcher satisfies |signals|, it + // will be returned in the next call to |GetReadyDispatchers()|. If + // |dispatcher| has been added, it must be removed before adding again, + // otherwise |MOJO_RESULT_ALREADY_EXISTS| will be returned. + virtual MojoResult AddWaitingDispatcher( + const scoped_refptr& dispatcher, + MojoHandleSignals signals, + uintptr_t context); + + // Removes a dispatcher to wait on. If |dispatcher| has not been added, + // |MOJO_RESULT_NOT_FOUND| will be returned. + virtual MojoResult RemoveWaitingDispatcher( + const scoped_refptr& dispatcher); + + // Returns a set of ready dispatchers. |*count| is the maximum number of + // dispatchers to return, and will contain the number of dispatchers returned + // in |dispatchers| on completion. + virtual MojoResult GetReadyDispatchers(uint32_t* count, + DispatcherVector* dispatchers, + MojoResult* results, + uintptr_t* contexts); + + ///////////// General-purpose API for all handle types ///////// + + // Gets the current handle signals state. (The default implementation simply + // returns a default-constructed |HandleSignalsState|, i.e., no signals + // satisfied or satisfiable.) Note: The state is subject to change from other + // threads. + virtual HandleSignalsState GetHandleSignalsState() const; + + // Adds a WatcherDispatcher reference to this dispatcher, to be notified of + // all subsequent changes to handle state including signal changes or closure. + // The reference is associated with a |context| for disambiguation of + // removals. + virtual MojoResult AddWatcherRef( + const scoped_refptr& watcher, + uintptr_t context); + + // Removes a WatcherDispatcher reference from this dispatcher. + virtual MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context); + + // Informs the caller of the total serialized size (in bytes) and the total + // number of platform handles and ports needed to transfer this dispatcher + // across a message pipe. + // + // Must eventually be followed by a call to EndSerializeAndClose(). Note that + // StartSerialize() and EndSerialize() are always called in sequence, and + // only between calls to BeginTransit() and either (but not both) + // CompleteTransitAndClose() or CancelTransit(). + // + // For this reason it is IMPERATIVE that the implementation ensure a + // consistent serializable state between BeginTransit() and + // CompleteTransitAndClose()/CancelTransit(). + virtual void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_platform_handles); + + // Serializes this dispatcher into |destination|, |ports|, and |handles|. + // Returns true iff successful, false otherwise. In either case the dispatcher + // will close. + // + // NOTE: Transit MAY still fail after this call returns. Implementations + // should not assume PlatformHandle ownership has transferred until + // CompleteTransitAndClose() is called. In other words, if CancelTransit() is + // called, the implementation should retain its PlatformHandles in working + // condition. + virtual bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles); + + // Does whatever is necessary to begin transit of the dispatcher. This + // should return |true| if transit is OK, or false if the underlying resource + // is deemed busy by the implementation. + virtual bool BeginTransit(); + + // Does whatever is necessary to complete transit of the dispatcher, including + // closure. This is only called upon successfully transmitting an outgoing + // message containing this serialized dispatcher. + virtual void CompleteTransitAndClose(); + + // Does whatever is necessary to cancel transit of the dispatcher. The + // dispatcher should remain in a working state and resume normal operation. + virtual void CancelTransit(); + + // Deserializes a specific dispatcher type from an incoming message. + static scoped_refptr Deserialize( + Type type, + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* platform_handles, + size_t num_platform_handles); + + protected: + friend class base::RefCountedThreadSafe; + + Dispatcher(); + virtual ~Dispatcher(); + + DISALLOW_COPY_AND_ASSIGN(Dispatcher); +}; + +// So logging macros and |DCHECK_EQ()|, etc. work. +MOJO_SYSTEM_IMPL_EXPORT inline std::ostream& operator<<(std::ostream& out, + Dispatcher::Type type) { + return out << static_cast(type); +} + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_DISPATCHER_H_ diff --git a/mojo/edk/system/handle_signals_state.h b/mojo/edk/system/handle_signals_state.h new file mode 100644 index 0000000000000000000000000000000000000000..f2412787cbf61ce31d1c8d16997d6981bb12c4e5 --- /dev/null +++ b/mojo/edk/system/handle_signals_state.h @@ -0,0 +1,13 @@ +// Copyright 2014 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 MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_ +#define MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_ + +#include "mojo/public/cpp/system/handle_signals_state.h" + +// TODO(rockot): Remove this header and use the C++ system library type +// directly inside the EDK. + +#endif // MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_ diff --git a/mojo/edk/system/handle_table.cc b/mojo/edk/system/handle_table.cc new file mode 100644 index 0000000000000000000000000000000000000000..b570793dbe0f4286dc204f76402fa29665c40b91 --- /dev/null +++ b/mojo/edk/system/handle_table.cc @@ -0,0 +1,135 @@ +// Copyright 2014 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. + +#include "mojo/edk/system/handle_table.h" + +#include + +#include + +namespace mojo { +namespace edk { + +HandleTable::HandleTable() {} + +HandleTable::~HandleTable() {} + +MojoHandle HandleTable::AddDispatcher(scoped_refptr dispatcher) { + // Oops, we're out of handles. + if (next_available_handle_ == MOJO_HANDLE_INVALID) + return MOJO_HANDLE_INVALID; + + MojoHandle handle = next_available_handle_++; + auto result = + handles_.insert(std::make_pair(handle, Entry(std::move(dispatcher)))); + DCHECK(result.second); + + return handle; +} + +bool HandleTable::AddDispatchersFromTransit( + const std::vector& dispatchers, + MojoHandle* handles) { + // Oops, we're out of handles. + if (next_available_handle_ == MOJO_HANDLE_INVALID) + return false; + + DCHECK_LE(dispatchers.size(), std::numeric_limits::max()); + // If this insertion would cause handle overflow, we're out of handles. + if (next_available_handle_ + dispatchers.size() < next_available_handle_) + return false; + + for (size_t i = 0; i < dispatchers.size(); ++i) { + MojoHandle handle = next_available_handle_++; + auto result = handles_.insert( + std::make_pair(handle, Entry(dispatchers[i].dispatcher))); + DCHECK(result.second); + handles[i] = handle; + } + + return true; +} + +scoped_refptr HandleTable::GetDispatcher(MojoHandle handle) const { + auto it = handles_.find(handle); + if (it == handles_.end()) + return nullptr; + return it->second.dispatcher; +} + +MojoResult HandleTable::GetAndRemoveDispatcher( + MojoHandle handle, + scoped_refptr* dispatcher) { + auto it = handles_.find(handle); + if (it == handles_.end()) + return MOJO_RESULT_INVALID_ARGUMENT; + if (it->second.busy) + return MOJO_RESULT_BUSY; + + *dispatcher = std::move(it->second.dispatcher); + handles_.erase(it); + return MOJO_RESULT_OK; +} + +MojoResult HandleTable::BeginTransit( + const MojoHandle* handles, + uint32_t num_handles, + std::vector* dispatchers) { + dispatchers->clear(); + dispatchers->reserve(num_handles); + for (size_t i = 0; i < num_handles; ++i) { + auto it = handles_.find(handles[i]); + if (it == handles_.end()) + return MOJO_RESULT_INVALID_ARGUMENT; + if (it->second.busy) + return MOJO_RESULT_BUSY; + + Dispatcher::DispatcherInTransit d; + d.local_handle = handles[i]; + d.dispatcher = it->second.dispatcher; + if (!d.dispatcher->BeginTransit()) + return MOJO_RESULT_BUSY; + it->second.busy = true; + dispatchers->push_back(d); + } + return MOJO_RESULT_OK; +} + +void HandleTable::CompleteTransitAndClose( + const std::vector& dispatchers) { + for (const auto& dispatcher : dispatchers) { + auto it = handles_.find(dispatcher.local_handle); + DCHECK(it != handles_.end() && it->second.busy); + handles_.erase(it); + dispatcher.dispatcher->CompleteTransitAndClose(); + } +} + +void HandleTable::CancelTransit( + const std::vector& dispatchers) { + for (const auto& dispatcher : dispatchers) { + auto it = handles_.find(dispatcher.local_handle); + DCHECK(it != handles_.end() && it->second.busy); + it->second.busy = false; + dispatcher.dispatcher->CancelTransit(); + } +} + +void HandleTable::GetActiveHandlesForTest(std::vector* handles) { + handles->clear(); + for (const auto& entry : handles_) + handles->push_back(entry.first); +} + +HandleTable::Entry::Entry() {} + +HandleTable::Entry::Entry(scoped_refptr dispatcher) + : dispatcher(std::move(dispatcher)) {} + +HandleTable::Entry::Entry(const Entry& other) = default; + +HandleTable::Entry::~Entry() {} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/handle_table.h b/mojo/edk/system/handle_table.h new file mode 100644 index 0000000000000000000000000000000000000000..882d5405cee83f4c7243a6d6706764e460324762 --- /dev/null +++ b/mojo/edk/system/handle_table.h @@ -0,0 +1,75 @@ +// Copyright 2014 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 MOJO_EDK_SYSTEM_HANDLE_TABLE_H_ +#define MOJO_EDK_SYSTEM_HANDLE_TABLE_H_ + +#include + +#include + +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { +namespace edk { + +class HandleTable { + public: + HandleTable(); + ~HandleTable(); + + MojoHandle AddDispatcher(scoped_refptr dispatcher); + + // Inserts multiple dispatchers received from message transit, populating + // |handles| with their newly allocated handles. Returns |true| on success. + bool AddDispatchersFromTransit( + const std::vector& dispatchers, + MojoHandle* handles); + + scoped_refptr GetDispatcher(MojoHandle handle) const; + MojoResult GetAndRemoveDispatcher(MojoHandle, + scoped_refptr* dispatcher); + + // Marks handles as busy and populates |dispatchers|. Returns MOJO_RESULT_BUSY + // if any of the handles are already in transit; MOJO_RESULT_INVALID_ARGUMENT + // if any of the handles are invalid; or MOJO_RESULT_OK if successful. + MojoResult BeginTransit( + const MojoHandle* handles, + uint32_t num_handles, + std::vector* dispatchers); + + void CompleteTransitAndClose( + const std::vector& dispatchers); + void CancelTransit( + const std::vector& dispatchers); + + void GetActiveHandlesForTest(std::vector *handles); + + private: + struct Entry { + Entry(); + explicit Entry(scoped_refptr dispatcher); + Entry(const Entry& other); + ~Entry(); + + scoped_refptr dispatcher; + bool busy = false; + }; + + using HandleMap = base::hash_map; + + HandleMap handles_; + + uint32_t next_available_handle_ = 1; + + DISALLOW_COPY_AND_ASSIGN(HandleTable); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_HANDLE_TABLE_H_ diff --git a/mojo/edk/system/mach_port_relay.cc b/mojo/edk/system/mach_port_relay.cc new file mode 100644 index 0000000000000000000000000000000000000000..f05cf22a9a5de4149f232ff546d86c7c3a2f1423 --- /dev/null +++ b/mojo/edk/system/mach_port_relay.cc @@ -0,0 +1,248 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/mach_port_relay.h" + +#include + +#include + +#include "base/logging.h" +#include "base/mac/mach_port_util.h" +#include "base/mac/scoped_mach_port.h" +#include "base/metrics/histogram_macros.h" +#include "base/process/process.h" +#include "mojo/edk/embedder/platform_handle_vector.h" + +namespace mojo { +namespace edk { + +namespace { + +// Errors that can occur in the broker (privileged parent) process. +// These match tools/metrics/histograms.xml. +// This enum is append-only. +enum class BrokerUMAError : int { + SUCCESS = 0, + // Couldn't get a task port for the process with a given pid. + ERROR_TASK_FOR_PID = 1, + // Couldn't make a port with receive rights in the destination process. + ERROR_MAKE_RECEIVE_PORT = 2, + // Couldn't change the attributes of a Mach port. + ERROR_SET_ATTRIBUTES = 3, + // Couldn't extract a right from the destination. + ERROR_EXTRACT_DEST_RIGHT = 4, + // Couldn't send a Mach port in a call to mach_msg(). + ERROR_SEND_MACH_PORT = 5, + // Couldn't extract a right from the source. + ERROR_EXTRACT_SOURCE_RIGHT = 6, + ERROR_MAX +}; + +// Errors that can occur in a child process. +// These match tools/metrics/histograms.xml. +// This enum is append-only. +enum class ChildUMAError : int { + SUCCESS = 0, + // An error occurred while trying to receive a Mach port with mach_msg(). + ERROR_RECEIVE_MACH_MESSAGE = 1, + ERROR_MAX +}; + +void ReportBrokerError(BrokerUMAError error) { + UMA_HISTOGRAM_ENUMERATION("Mojo.MachPortRelay.BrokerError", + static_cast(error), + static_cast(BrokerUMAError::ERROR_MAX)); +} + +void ReportChildError(ChildUMAError error) { + UMA_HISTOGRAM_ENUMERATION("Mojo.MachPortRelay.ChildError", + static_cast(error), + static_cast(ChildUMAError::ERROR_MAX)); +} + +} // namespace + +// static +bool MachPortRelay::ReceivePorts(PlatformHandleVector* handles) { + DCHECK(handles); + + for (size_t i = 0; i < handles->size(); i++) { + PlatformHandle* handle = handles->data() + i; + DCHECK(handle->type != PlatformHandle::Type::MACH); + if (handle->type != PlatformHandle::Type::MACH_NAME) + continue; + + if (handle->port == MACH_PORT_NULL) { + handle->type = PlatformHandle::Type::MACH; + continue; + } + + base::mac::ScopedMachReceiveRight message_port(handle->port); + base::mac::ScopedMachSendRight received_port( + base::ReceiveMachPort(message_port.get())); + if (received_port.get() == MACH_PORT_NULL) { + ReportChildError(ChildUMAError::ERROR_RECEIVE_MACH_MESSAGE); + handle->port = MACH_PORT_NULL; + LOG(ERROR) << "Error receiving mach port"; + return false; + } + + ReportChildError(ChildUMAError::SUCCESS); + handle->port = received_port.release(); + handle->type = PlatformHandle::Type::MACH; + } + + return true; +} + +MachPortRelay::MachPortRelay(base::PortProvider* port_provider) + : port_provider_(port_provider) { + DCHECK(port_provider); + port_provider_->AddObserver(this); +} + +MachPortRelay::~MachPortRelay() { + port_provider_->RemoveObserver(this); +} + +bool MachPortRelay::SendPortsToProcess(Channel::Message* message, + base::ProcessHandle process) { + DCHECK(message); + mach_port_t task_port = port_provider_->TaskForPid(process); + if (task_port == MACH_PORT_NULL) { + // Callers check the port provider for the task port before calling this + // function, in order to queue pending messages. Therefore, if this fails, + // it should be considered a genuine, bona fide, electrified, six-car error. + ReportBrokerError(BrokerUMAError::ERROR_TASK_FOR_PID); + return false; + } + + size_t num_sent = 0; + bool error = false; + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + // Message should have handles, otherwise there's no point in calling this + // function. + DCHECK(handles); + for (size_t i = 0; i < handles->size(); i++) { + PlatformHandle* handle = &(*handles)[i]; + DCHECK(handle->type != PlatformHandle::Type::MACH_NAME); + if (handle->type != PlatformHandle::Type::MACH) + continue; + + if (handle->port == MACH_PORT_NULL) { + handle->type = PlatformHandle::Type::MACH_NAME; + num_sent++; + continue; + } + + mach_port_name_t intermediate_port; + base::MachCreateError error_code; + intermediate_port = base::CreateIntermediateMachPort( + task_port, base::mac::ScopedMachSendRight(handle->port), &error_code); + if (intermediate_port == MACH_PORT_NULL) { + BrokerUMAError uma_error; + switch (error_code) { + case base::MachCreateError::ERROR_MAKE_RECEIVE_PORT: + uma_error = BrokerUMAError::ERROR_MAKE_RECEIVE_PORT; + break; + case base::MachCreateError::ERROR_SET_ATTRIBUTES: + uma_error = BrokerUMAError::ERROR_SET_ATTRIBUTES; + break; + case base::MachCreateError::ERROR_EXTRACT_DEST_RIGHT: + uma_error = BrokerUMAError::ERROR_EXTRACT_DEST_RIGHT; + break; + case base::MachCreateError::ERROR_SEND_MACH_PORT: + uma_error = BrokerUMAError::ERROR_SEND_MACH_PORT; + break; + } + ReportBrokerError(uma_error); + handle->port = MACH_PORT_NULL; + error = true; + break; + } + + ReportBrokerError(BrokerUMAError::SUCCESS); + handle->port = intermediate_port; + handle->type = PlatformHandle::Type::MACH_NAME; + num_sent++; + } + DCHECK(error || num_sent); + message->SetHandles(std::move(handles)); + + return !error; +} + +bool MachPortRelay::ExtractPortRights(Channel::Message* message, + base::ProcessHandle process) { + DCHECK(message); + + mach_port_t task_port = port_provider_->TaskForPid(process); + if (task_port == MACH_PORT_NULL) { + ReportBrokerError(BrokerUMAError::ERROR_TASK_FOR_PID); + return false; + } + + size_t num_received = 0; + bool error = false; + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + // Message should have handles, otherwise there's no point in calling this + // function. + DCHECK(handles); + for (size_t i = 0; i < handles->size(); i++) { + PlatformHandle* handle = handles->data() + i; + DCHECK(handle->type != PlatformHandle::Type::MACH); + if (handle->type != PlatformHandle::Type::MACH_NAME) + continue; + + if (handle->port == MACH_PORT_NULL) { + handle->type = PlatformHandle::Type::MACH; + num_received++; + continue; + } + + mach_port_t extracted_right = MACH_PORT_NULL; + mach_msg_type_name_t extracted_right_type; + kern_return_t kr = + mach_port_extract_right(task_port, handle->port, + MACH_MSG_TYPE_MOVE_SEND, + &extracted_right, &extracted_right_type); + if (kr != KERN_SUCCESS) { + ReportBrokerError(BrokerUMAError::ERROR_EXTRACT_SOURCE_RIGHT); + error = true; + break; + } + + ReportBrokerError(BrokerUMAError::SUCCESS); + DCHECK_EQ(static_cast(MACH_MSG_TYPE_PORT_SEND), + extracted_right_type); + handle->port = extracted_right; + handle->type = PlatformHandle::Type::MACH; + num_received++; + } + DCHECK(error || num_received); + message->SetHandles(std::move(handles)); + + return !error; +} + +void MachPortRelay::AddObserver(Observer* observer) { + base::AutoLock locker(observers_lock_); + bool inserted = observers_.insert(observer).second; + DCHECK(inserted); +} + +void MachPortRelay::RemoveObserver(Observer* observer) { + base::AutoLock locker(observers_lock_); + observers_.erase(observer); +} + +void MachPortRelay::OnReceivedTaskPort(base::ProcessHandle process) { + base::AutoLock locker(observers_lock_); + for (auto* observer : observers_) + observer->OnProcessReady(process); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/mach_port_relay.h b/mojo/edk/system/mach_port_relay.h new file mode 100644 index 0000000000000000000000000000000000000000..87bc56cf5bb31e81451f01e131889ec60f67e48e --- /dev/null +++ b/mojo/edk/system/mach_port_relay.h @@ -0,0 +1,94 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_ +#define MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_ + +#include + +#include "base/macros.h" +#include "base/process/port_provider_mac.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/channel.h" + +namespace mojo { +namespace edk { + +// The MachPortRelay is used by a privileged process, usually the root process, +// to manipulate Mach ports in a child process. Ports can be added to and +// extracted from a child process that has registered itself with the +// |base::PortProvider| used by this class. +class MachPortRelay : public base::PortProvider::Observer { + public: + class Observer { + public: + // Called by the MachPortRelay to notify observers that a new process is + // ready for Mach ports to be sent/received. There are no guarantees about + // the thread this is called on, including the presence of a MessageLoop. + // Implementations must not call AddObserver() or RemoveObserver() during + // this function, as doing so will deadlock. + virtual void OnProcessReady(base::ProcessHandle process) = 0; + }; + + // Used by a child process to receive Mach ports from a sender (privileged) + // process. Each Mach port in |handles| is interpreted as an intermediate Mach + // port. It replaces each Mach port with the final Mach port received from the + // intermediate port. This method takes ownership of the intermediate Mach + // port and gives ownership of the final Mach port to the caller. Any handles + // that are not Mach ports will remain unchanged, and the number and ordering + // of handles is preserved. + // Returns |false| on failure and there is no guarantee about whether a Mach + // port is intermediate or final. + // + // See SendPortsToProcess() for the definition of intermediate and final Mach + // ports. + static bool ReceivePorts(PlatformHandleVector* handles); + + explicit MachPortRelay(base::PortProvider* port_provider); + ~MachPortRelay() override; + + // Sends the Mach ports attached to |message| to |process|. + // For each Mach port attached to |message|, a new Mach port, the intermediate + // port, is created in |process|. The message's Mach port is then sent over + // this intermediate port and the message is modified to refer to the name of + // the intermediate port. The Mach port received over the intermediate port in + // the child is referred to as the final Mach port. + // Returns |false| on failure and |message| may contain a mix of actual Mach + // ports and names. + bool SendPortsToProcess(Channel::Message* message, + base::ProcessHandle process); + + // Extracts the Mach ports attached to |message| from |process|. + // Any Mach ports attached to |message| are names and not actual Mach ports + // that are valid in this process. For each of those Mach port names, a send + // right is extracted from |process| and the port name is replaced with the + // send right. + // Returns |false| on failure and |message| may contain a mix of actual Mach + // ports and names. + bool ExtractPortRights(Channel::Message* message, + base::ProcessHandle process); + + // Observer interface. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + base::PortProvider* port_provider() const { return port_provider_; } + + private: + // base::PortProvider::Observer implementation. + void OnReceivedTaskPort(base::ProcessHandle process) override; + + base::PortProvider* const port_provider_; + + base::Lock observers_lock_; + std::set observers_; + + DISALLOW_COPY_AND_ASSIGN(MachPortRelay); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_ diff --git a/mojo/edk/system/mapping_table.cc b/mojo/edk/system/mapping_table.cc new file mode 100644 index 0000000000000000000000000000000000000000..850944306eab6951b580fe190808d28ae02f09a8 --- /dev/null +++ b/mojo/edk/system/mapping_table.cc @@ -0,0 +1,48 @@ +// Copyright 2014 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. + +#include "mojo/edk/system/mapping_table.h" + +#include "base/logging.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/configuration.h" + +namespace mojo { +namespace edk { + +MappingTable::MappingTable() { +} + +MappingTable::~MappingTable() { + // This should usually not be reached (the only instance should be owned by + // the singleton |Core|, which lives forever), except in tests. +} + +MojoResult MappingTable::AddMapping( + std::unique_ptr mapping) { + DCHECK(mapping); + + if (address_to_mapping_map_.size() >= + GetConfiguration().max_mapping_table_sze) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + void* address = mapping->GetBase(); + DCHECK(address_to_mapping_map_.find(address) == + address_to_mapping_map_.end()); + address_to_mapping_map_[address] = mapping.release(); + return MOJO_RESULT_OK; +} + +MojoResult MappingTable::RemoveMapping(void* address) { + AddressToMappingMap::iterator it = address_to_mapping_map_.find(address); + if (it == address_to_mapping_map_.end()) + return MOJO_RESULT_INVALID_ARGUMENT; + PlatformSharedBufferMapping* mapping_to_delete = it->second; + address_to_mapping_map_.erase(it); + delete mapping_to_delete; + return MOJO_RESULT_OK; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/mapping_table.h b/mojo/edk/system/mapping_table.h new file mode 100644 index 0000000000000000000000000000000000000000..00167e36048196894fe31a50db891bc9b18d55a6 --- /dev/null +++ b/mojo/edk/system/mapping_table.h @@ -0,0 +1,57 @@ +// Copyright 2014 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 MOJO_EDK_SYSTEM_MAPPING_TABLE_H_ +#define MOJO_EDK_SYSTEM_MAPPING_TABLE_H_ + +#include + +#include +#include + +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { + +namespace edk { +class Core; +class PlatformSharedBufferMapping; + +// Test-only function (defined/used in embedder/test_embedder.cc). Declared here +// so it can be friended. +namespace internal { +bool ShutdownCheckNoLeaks(Core*); +} + +// This class provides the (global) table of memory mappings (owned by |Core|), +// which maps mapping base addresses to |PlatformSharedBufferMapping|s. +// +// This class is NOT thread-safe; locking is left to |Core|. +class MOJO_SYSTEM_IMPL_EXPORT MappingTable { + public: + MappingTable(); + ~MappingTable(); + + // Tries to add a mapping. (Takes ownership of the mapping in all cases; on + // failure, it will be destroyed.) + MojoResult AddMapping(std::unique_ptr mapping); + MojoResult RemoveMapping(void* address); + + private: + friend bool internal::ShutdownCheckNoLeaks(Core*); + + using AddressToMappingMap = + base::hash_map; + AddressToMappingMap address_to_mapping_map_; + + DISALLOW_COPY_AND_ASSIGN(MappingTable); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_MAPPING_TABLE_H_ diff --git a/mojo/edk/system/message_for_transit.cc b/mojo/edk/system/message_for_transit.cc new file mode 100644 index 0000000000000000000000000000000000000000..26658e161c630b0386fe98afe62abedd067e68c6 --- /dev/null +++ b/mojo/edk/system/message_for_transit.cc @@ -0,0 +1,136 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/message_for_transit.h" + +#include + +#include "mojo/edk/embedder/platform_handle_vector.h" + +namespace mojo { +namespace edk { + +namespace { + +static_assert(sizeof(MessageForTransit::MessageHeader) % 8 == 0, + "Invalid MessageHeader size."); +static_assert(sizeof(MessageForTransit::DispatcherHeader) % 8 == 0, + "Invalid DispatcherHeader size."); + +} // namespace + +MessageForTransit::~MessageForTransit() {} + +// static +MojoResult MessageForTransit::Create( + std::unique_ptr* message, + uint32_t num_bytes, + const Dispatcher::DispatcherInTransit* dispatchers, + uint32_t num_dispatchers) { + // A structure for retaining information about every Dispatcher that will be + // sent with this message. + struct DispatcherInfo { + uint32_t num_bytes; + uint32_t num_ports; + uint32_t num_handles; + }; + + // This is only the base header size. It will grow as we accumulate the + // size of serialized state for each dispatcher. + size_t header_size = sizeof(MessageHeader) + + num_dispatchers * sizeof(DispatcherHeader); + size_t num_ports = 0; + size_t num_handles = 0; + + std::vector dispatcher_info(num_dispatchers); + for (size_t i = 0; i < num_dispatchers; ++i) { + Dispatcher* d = dispatchers[i].dispatcher.get(); + d->StartSerialize(&dispatcher_info[i].num_bytes, + &dispatcher_info[i].num_ports, + &dispatcher_info[i].num_handles); + header_size += dispatcher_info[i].num_bytes; + num_ports += dispatcher_info[i].num_ports; + num_handles += dispatcher_info[i].num_handles; + } + + // We now have enough information to fully allocate the message storage. + std::unique_ptr msg = PortsMessage::NewUserMessage( + header_size + num_bytes, num_ports, num_handles); + if (!msg) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + // Populate the message header with information about serialized dispatchers. + // + // The front of the message is always a MessageHeader followed by a + // DispatcherHeader for each dispatcher to be sent. + MessageHeader* header = + static_cast(msg->mutable_payload_bytes()); + DispatcherHeader* dispatcher_headers = + reinterpret_cast(header + 1); + + // Serialized dispatcher state immediately follows the series of + // DispatcherHeaders. + char* dispatcher_data = + reinterpret_cast(dispatcher_headers + num_dispatchers); + + header->num_dispatchers = num_dispatchers; + + // |header_size| is the total number of bytes preceding the message payload, + // including all dispatcher headers and serialized dispatcher state. + DCHECK_LE(header_size, std::numeric_limits::max()); + header->header_size = static_cast(header_size); + + if (num_dispatchers > 0) { + ScopedPlatformHandleVectorPtr handles( + new PlatformHandleVector(num_handles)); + size_t port_index = 0; + size_t handle_index = 0; + bool fail = false; + for (size_t i = 0; i < num_dispatchers; ++i) { + Dispatcher* d = dispatchers[i].dispatcher.get(); + DispatcherHeader* dh = &dispatcher_headers[i]; + const DispatcherInfo& info = dispatcher_info[i]; + + // Fill in the header for this dispatcher. + dh->type = static_cast(d->GetType()); + dh->num_bytes = info.num_bytes; + dh->num_ports = info.num_ports; + dh->num_platform_handles = info.num_handles; + + // Fill in serialized state, ports, and platform handles. We'll cancel + // the send if the dispatcher implementation rejects for some reason. + if (!d->EndSerialize(static_cast(dispatcher_data), + msg->mutable_ports() + port_index, + handles->data() + handle_index)) { + fail = true; + break; + } + + dispatcher_data += info.num_bytes; + port_index += info.num_ports; + handle_index += info.num_handles; + } + + if (fail) { + // Release any platform handles we've accumulated. Their dispatchers + // retain ownership when message creation fails, so these are not actually + // leaking. + handles->clear(); + return MOJO_RESULT_INVALID_ARGUMENT; + } + + // Take ownership of all the handles and move them into message storage. + msg->SetHandles(std::move(handles)); + } + + message->reset(new MessageForTransit(std::move(msg))); + return MOJO_RESULT_OK; +} + +MessageForTransit::MessageForTransit(std::unique_ptr message) + : message_(std::move(message)) { +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/message_for_transit.h b/mojo/edk/system/message_for_transit.h new file mode 100644 index 0000000000000000000000000000000000000000..6103a771e1be5716046a33eadf55c4357998a6b5 --- /dev/null +++ b/mojo/edk/system/message_for_transit.h @@ -0,0 +1,115 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_MESSAGE_FOR_TRANSIT_H_ +#define MOJO_EDK_SYSTEM_MESSAGE_FOR_TRANSIT_H_ + +#include + +#include + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/ports_message.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +// MessageForTransit holds onto a PortsMessage which may be sent via +// |MojoWriteMessage()| or which may have been received on a pipe endpoint. +// Instances of this class are exposed to Mojo system API consumers via the +// opaque pointers used with |MojoCreateMessage()|, |MojoDestroyMessage()|, +// |MojoWriteMessageNew()|, and |MojoReadMessageNew()|. +class MOJO_SYSTEM_IMPL_EXPORT MessageForTransit { + public: +#pragma pack(push, 1) + // Header attached to every message. + struct MessageHeader { + // The number of serialized dispatchers included in this header. + uint32_t num_dispatchers; + + // Total size of the header, including serialized dispatcher data. + uint32_t header_size; + }; + + // Header for each dispatcher in a message, immediately following the message + // header. + struct DispatcherHeader { + // The type of the dispatcher, correpsonding to the Dispatcher::Type enum. + int32_t type; + + // The size of the serialized dispatcher, not including this header. + uint32_t num_bytes; + + // The number of ports needed to deserialize this dispatcher. + uint32_t num_ports; + + // The number of platform handles needed to deserialize this dispatcher. + uint32_t num_platform_handles; + }; +#pragma pack(pop) + + ~MessageForTransit(); + + // A static constructor for building outbound messages. + static MojoResult Create( + std::unique_ptr* message, + uint32_t num_bytes, + const Dispatcher::DispatcherInTransit* dispatchers, + uint32_t num_dispatchers); + + // A static constructor for wrapping inbound messages. + static std::unique_ptr WrapPortsMessage( + std::unique_ptr message) { + return base::WrapUnique(new MessageForTransit(std::move(message))); + } + + const void* bytes() const { + DCHECK(message_); + return static_cast( + static_cast(message_->payload_bytes()) + + header()->header_size); + } + + void* mutable_bytes() { + DCHECK(message_); + return static_cast( + static_cast(message_->mutable_payload_bytes()) + + header()->header_size); + } + + size_t num_bytes() const { + size_t header_size = header()->header_size; + DCHECK_GE(message_->num_payload_bytes(), header_size); + return message_->num_payload_bytes() - header_size; + } + + size_t num_handles() const { return header()->num_dispatchers; } + + const PortsMessage& ports_message() const { return *message_; } + + std::unique_ptr TakePortsMessage() { + return std::move(message_); + } + + private: + explicit MessageForTransit(std::unique_ptr message); + + const MessageForTransit::MessageHeader* header() const { + DCHECK(message_); + return static_cast( + message_->payload_bytes()); + } + + std::unique_ptr message_; + + DISALLOW_COPY_AND_ASSIGN(MessageForTransit); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_MESSAGE_FOR_TRANSIT_H_ diff --git a/mojo/edk/system/message_pipe_dispatcher.cc b/mojo/edk/system/message_pipe_dispatcher.cc new file mode 100644 index 0000000000000000000000000000000000000000..1db56c0daca0941e3db65592a45650f926a400f2 --- /dev/null +++ b/mojo/edk/system/message_pipe_dispatcher.cc @@ -0,0 +1,554 @@ +// 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. + +#include "mojo/edk/system/message_pipe_dispatcher.h" + +#include +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/message_for_transit.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/ports/message_filter.h" +#include "mojo/edk/system/ports_message.h" +#include "mojo/edk/system/request_context.h" + +namespace mojo { +namespace edk { + +namespace { + +using DispatcherHeader = MessageForTransit::DispatcherHeader; +using MessageHeader = MessageForTransit::MessageHeader; + +#pragma pack(push, 1) + +struct SerializedState { + uint64_t pipe_id; + int8_t endpoint; + char padding[7]; +}; + +static_assert(sizeof(SerializedState) % 8 == 0, + "Invalid SerializedState size."); + +#pragma pack(pop) + +} // namespace + +// A PortObserver which forwards to a MessagePipeDispatcher. This owns a +// reference to the MPD to ensure it lives as long as the observed port. +class MessagePipeDispatcher::PortObserverThunk + : public NodeController::PortObserver { + public: + explicit PortObserverThunk(scoped_refptr dispatcher) + : dispatcher_(dispatcher) {} + + private: + ~PortObserverThunk() override {} + + // NodeController::PortObserver: + void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); } + + scoped_refptr dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(PortObserverThunk); +}; + +// A MessageFilter used by ReadMessage to determine whether a message should +// actually be consumed yet. +class ReadMessageFilter : public ports::MessageFilter { + public: + // Creates a new ReadMessageFilter which captures and potentially modifies + // various (unowned) local state within MessagePipeDispatcher::ReadMessage. + ReadMessageFilter(bool read_any_size, + bool may_discard, + uint32_t* num_bytes, + uint32_t* num_handles, + bool* no_space, + bool* invalid_message) + : read_any_size_(read_any_size), + may_discard_(may_discard), + num_bytes_(num_bytes), + num_handles_(num_handles), + no_space_(no_space), + invalid_message_(invalid_message) {} + + ~ReadMessageFilter() override {} + + // ports::MessageFilter: + bool Match(const ports::Message& m) override { + const PortsMessage& message = static_cast(m); + if (message.num_payload_bytes() < sizeof(MessageHeader)) { + *invalid_message_ = true; + return true; + } + + const MessageHeader* header = + static_cast(message.payload_bytes()); + if (header->header_size > message.num_payload_bytes()) { + *invalid_message_ = true; + return true; + } + + uint32_t bytes_to_read = 0; + uint32_t bytes_available = + static_cast(message.num_payload_bytes()) - + header->header_size; + if (num_bytes_) { + bytes_to_read = std::min(*num_bytes_, bytes_available); + *num_bytes_ = bytes_available; + } + + uint32_t handles_to_read = 0; + uint32_t handles_available = header->num_dispatchers; + if (num_handles_) { + handles_to_read = std::min(*num_handles_, handles_available); + *num_handles_ = handles_available; + } + + if (handles_to_read < handles_available || + (!read_any_size_ && bytes_to_read < bytes_available)) { + *no_space_ = true; + return may_discard_; + } + + return true; + } + + private: + const bool read_any_size_; + const bool may_discard_; + uint32_t* const num_bytes_; + uint32_t* const num_handles_; + bool* const no_space_; + bool* const invalid_message_; + + DISALLOW_COPY_AND_ASSIGN(ReadMessageFilter); +}; + +#if DCHECK_IS_ON() + +// A MessageFilter which never matches a message. Used to peek at the size of +// the next available message on a port, for debug logging only. +class PeekSizeMessageFilter : public ports::MessageFilter { + public: + PeekSizeMessageFilter() {} + ~PeekSizeMessageFilter() override {} + + // ports::MessageFilter: + bool Match(const ports::Message& message) override { + message_size_ = message.num_payload_bytes(); + return false; + } + + size_t message_size() const { return message_size_; } + + private: + size_t message_size_ = 0; + + DISALLOW_COPY_AND_ASSIGN(PeekSizeMessageFilter); +}; + +#endif // DCHECK_IS_ON() + +MessagePipeDispatcher::MessagePipeDispatcher(NodeController* node_controller, + const ports::PortRef& port, + uint64_t pipe_id, + int endpoint) + : node_controller_(node_controller), + port_(port), + pipe_id_(pipe_id), + endpoint_(endpoint), + watchers_(this) { + DVLOG(2) << "Creating new MessagePipeDispatcher for port " << port.name() + << " [pipe_id=" << pipe_id << "; endpoint=" << endpoint << "]"; + + node_controller_->SetPortObserver( + port_, + make_scoped_refptr(new PortObserverThunk(this))); +} + +bool MessagePipeDispatcher::Fuse(MessagePipeDispatcher* other) { + node_controller_->SetPortObserver(port_, nullptr); + node_controller_->SetPortObserver(other->port_, nullptr); + + ports::PortRef port0; + { + base::AutoLock lock(signal_lock_); + port0 = port_; + port_closed_.Set(true); + watchers_.NotifyClosed(); + } + + ports::PortRef port1; + { + base::AutoLock lock(other->signal_lock_); + port1 = other->port_; + other->port_closed_.Set(true); + other->watchers_.NotifyClosed(); + } + + // Both ports are always closed by this call. + int rv = node_controller_->MergeLocalPorts(port0, port1); + return rv == ports::OK; +} + +Dispatcher::Type MessagePipeDispatcher::GetType() const { + return Type::MESSAGE_PIPE; +} + +MojoResult MessagePipeDispatcher::Close() { + base::AutoLock lock(signal_lock_); + DVLOG(2) << "Closing message pipe " << pipe_id_ << " endpoint " << endpoint_ + << " [port=" << port_.name() << "]"; + return CloseNoLock(); +} + +MojoResult MessagePipeDispatcher::WriteMessage( + std::unique_ptr message, + MojoWriteMessageFlags flags) { + if (port_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + size_t num_bytes = message->num_bytes(); + int rv = node_controller_->SendMessage(port_, message->TakePortsMessage()); + + DVLOG(4) << "Sent message on pipe " << pipe_id_ << " endpoint " << endpoint_ + << " [port=" << port_.name() << "; rv=" << rv + << "; num_bytes=" << num_bytes << "]"; + + if (rv != ports::OK) { + if (rv == ports::ERROR_PORT_UNKNOWN || + rv == ports::ERROR_PORT_STATE_UNEXPECTED || + rv == ports::ERROR_PORT_CANNOT_SEND_PEER) { + return MOJO_RESULT_INVALID_ARGUMENT; + } else if (rv == ports::ERROR_PORT_PEER_CLOSED) { + return MOJO_RESULT_FAILED_PRECONDITION; + } + + NOTREACHED(); + return MOJO_RESULT_UNKNOWN; + } + + return MOJO_RESULT_OK; +} + +MojoResult MessagePipeDispatcher::ReadMessage( + std::unique_ptr* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags, + bool read_any_size) { + // We can't read from a port that's closed or in transit! + if (port_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + bool no_space = false; + bool may_discard = flags & MOJO_READ_MESSAGE_FLAG_MAY_DISCARD; + bool invalid_message = false; + + // Grab a message if the provided handles buffer is large enough. If the input + // |num_bytes| is provided and |read_any_size| is false, we also ensure + // that it specifies a size at least as large as the next available payload. + // + // If |read_any_size| is true, the input value of |*num_bytes| is ignored. + // This flag exists to support both new and old API behavior. + + ports::ScopedMessage ports_message; + ReadMessageFilter filter(read_any_size, may_discard, num_bytes, num_handles, + &no_space, &invalid_message); + int rv = node_controller_->node()->GetMessage(port_, &ports_message, &filter); + + if (invalid_message) + return MOJO_RESULT_UNKNOWN; + + if (rv != ports::OK && rv != ports::ERROR_PORT_PEER_CLOSED) { + if (rv == ports::ERROR_PORT_UNKNOWN || + rv == ports::ERROR_PORT_STATE_UNEXPECTED) + return MOJO_RESULT_INVALID_ARGUMENT; + + NOTREACHED(); + return MOJO_RESULT_UNKNOWN; // TODO: Add a better error code here? + } + + if (no_space) { + if (may_discard) { + // May have been the last message on the pipe. Need to update signals just + // in case. + base::AutoLock lock(signal_lock_); + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + } + // |*num_handles| (and/or |*num_bytes| if |read_any_size| is false) wasn't + // sufficient to hold this message's data. The message will still be in + // queue unless MOJO_READ_MESSAGE_FLAG_MAY_DISCARD was set. + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + if (!ports_message) { + // No message was available in queue. + + if (rv == ports::OK) + return MOJO_RESULT_SHOULD_WAIT; + + // Peer is closed and there are no more messages to read. + DCHECK_EQ(rv, ports::ERROR_PORT_PEER_CLOSED); + return MOJO_RESULT_FAILED_PRECONDITION; + } + + // Alright! We have a message and the caller has provided sufficient storage + // in which to receive it. + + { + // We need to update anyone watching our signals in case that was the last + // available message. + base::AutoLock lock(signal_lock_); + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + } + + std::unique_ptr msg( + static_cast(ports_message.release())); + + const MessageHeader* header = + static_cast(msg->payload_bytes()); + const DispatcherHeader* dispatcher_headers = + reinterpret_cast(header + 1); + + if (header->num_dispatchers > std::numeric_limits::max()) + return MOJO_RESULT_UNKNOWN; + + // Deserialize dispatchers. + if (header->num_dispatchers > 0) { + CHECK(handles); + std::vector dispatchers(header->num_dispatchers); + size_t data_payload_index = sizeof(MessageHeader) + + header->num_dispatchers * sizeof(DispatcherHeader); + if (data_payload_index > header->header_size) + return MOJO_RESULT_UNKNOWN; + const char* dispatcher_data = reinterpret_cast( + dispatcher_headers + header->num_dispatchers); + size_t port_index = 0; + size_t platform_handle_index = 0; + ScopedPlatformHandleVectorPtr msg_handles = msg->TakeHandles(); + const size_t num_msg_handles = msg_handles ? msg_handles->size() : 0; + for (size_t i = 0; i < header->num_dispatchers; ++i) { + const DispatcherHeader& dh = dispatcher_headers[i]; + Type type = static_cast(dh.type); + + size_t next_payload_index = data_payload_index + dh.num_bytes; + if (msg->num_payload_bytes() < next_payload_index || + next_payload_index < data_payload_index) { + return MOJO_RESULT_UNKNOWN; + } + + size_t next_port_index = port_index + dh.num_ports; + if (msg->num_ports() < next_port_index || next_port_index < port_index) + return MOJO_RESULT_UNKNOWN; + + size_t next_platform_handle_index = + platform_handle_index + dh.num_platform_handles; + if (num_msg_handles < next_platform_handle_index || + next_platform_handle_index < platform_handle_index) { + return MOJO_RESULT_UNKNOWN; + } + + PlatformHandle* out_handles = + num_msg_handles ? msg_handles->data() + platform_handle_index + : nullptr; + dispatchers[i].dispatcher = Dispatcher::Deserialize( + type, dispatcher_data, dh.num_bytes, msg->ports() + port_index, + dh.num_ports, out_handles, dh.num_platform_handles); + if (!dispatchers[i].dispatcher) + return MOJO_RESULT_UNKNOWN; + + dispatcher_data += dh.num_bytes; + data_payload_index = next_payload_index; + port_index = next_port_index; + platform_handle_index = next_platform_handle_index; + } + + if (!node_controller_->core()->AddDispatchersFromTransit(dispatchers, + handles)) + return MOJO_RESULT_UNKNOWN; + } + + CHECK(msg); + *message = MessageForTransit::WrapPortsMessage(std::move(msg)); + return MOJO_RESULT_OK; +} + +HandleSignalsState +MessagePipeDispatcher::GetHandleSignalsState() const { + base::AutoLock lock(signal_lock_); + return GetHandleSignalsStateNoLock(); +} + +MojoResult MessagePipeDispatcher::AddWatcherRef( + const scoped_refptr& watcher, + uintptr_t context) { + base::AutoLock lock(signal_lock_); + if (port_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock()); +} + +MojoResult MessagePipeDispatcher::RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) { + base::AutoLock lock(signal_lock_); + if (port_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Remove(watcher, context); +} + +void MessagePipeDispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) { + *num_bytes = static_cast(sizeof(SerializedState)); + *num_ports = 1; + *num_handles = 0; +} + +bool MessagePipeDispatcher::EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) { + SerializedState* state = static_cast(destination); + state->pipe_id = pipe_id_; + state->endpoint = static_cast(endpoint_); + memset(state->padding, 0, sizeof(state->padding)); + ports[0] = port_.name(); + return true; +} + +bool MessagePipeDispatcher::BeginTransit() { + base::AutoLock lock(signal_lock_); + if (in_transit_ || port_closed_) + return false; + in_transit_.Set(true); + return in_transit_; +} + +void MessagePipeDispatcher::CompleteTransitAndClose() { + node_controller_->SetPortObserver(port_, nullptr); + + base::AutoLock lock(signal_lock_); + port_transferred_ = true; + in_transit_.Set(false); + CloseNoLock(); +} + +void MessagePipeDispatcher::CancelTransit() { + base::AutoLock lock(signal_lock_); + in_transit_.Set(false); + + // Something may have happened while we were waiting for potential transit. + watchers_.NotifyState(GetHandleSignalsStateNoLock()); +} + +// static +scoped_refptr MessagePipeDispatcher::Deserialize( + const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles) { + if (num_ports != 1 || num_handles || num_bytes != sizeof(SerializedState)) + return nullptr; + + const SerializedState* state = static_cast(data); + + ports::PortRef port; + CHECK_EQ( + ports::OK, + internal::g_core->GetNodeController()->node()->GetPort(ports[0], &port)); + + return new MessagePipeDispatcher(internal::g_core->GetNodeController(), port, + state->pipe_id, state->endpoint); +} + +MessagePipeDispatcher::~MessagePipeDispatcher() { + DCHECK(port_closed_ && !in_transit_); +} + +MojoResult MessagePipeDispatcher::CloseNoLock() { + signal_lock_.AssertAcquired(); + if (port_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + port_closed_.Set(true); + watchers_.NotifyClosed(); + + if (!port_transferred_) { + base::AutoUnlock unlock(signal_lock_); + node_controller_->ClosePort(port_); + } + + return MOJO_RESULT_OK; +} + +HandleSignalsState MessagePipeDispatcher::GetHandleSignalsStateNoLock() const { + HandleSignalsState rv; + + ports::PortStatus port_status; + if (node_controller_->node()->GetStatus(port_, &port_status) != ports::OK) { + CHECK(in_transit_ || port_transferred_ || port_closed_); + return HandleSignalsState(); + } + + if (port_status.has_messages) { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_READABLE; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + } + if (port_status.receiving_messages) + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + if (!port_status.peer_closed) { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + } else { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + } + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + return rv; +} + +void MessagePipeDispatcher::OnPortStatusChanged() { + DCHECK(RequestContext::current()); + + base::AutoLock lock(signal_lock_); + + // We stop observing our port as soon as it's transferred, but this can race + // with events which are raised right before that happens. This is fine to + // ignore. + if (port_transferred_) + return; + +#if DCHECK_IS_ON() + ports::PortStatus port_status; + if (node_controller_->node()->GetStatus(port_, &port_status) == ports::OK) { + if (port_status.has_messages) { + ports::ScopedMessage unused; + PeekSizeMessageFilter filter; + node_controller_->node()->GetMessage(port_, &unused, &filter); + DVLOG(4) << "New message detected on message pipe " << pipe_id_ + << " endpoint " << endpoint_ << " [port=" << port_.name() + << "; size=" << filter.message_size() << "]"; + } + if (port_status.peer_closed) { + DVLOG(2) << "Peer closure detected on message pipe " << pipe_id_ + << " endpoint " << endpoint_ << " [port=" << port_.name() << "]"; + } + } +#endif + + watchers_.NotifyState(GetHandleSignalsStateNoLock()); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/message_pipe_dispatcher.h b/mojo/edk/system/message_pipe_dispatcher.h new file mode 100644 index 0000000000000000000000000000000000000000..574ad660b04f4bcb9b952c15cb7cb3edb74c9265 --- /dev/null +++ b/mojo/edk/system/message_pipe_dispatcher.h @@ -0,0 +1,115 @@ +// 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. + +#ifndef MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_ + +#include + +#include +#include + +#include "base/macros.h" +#include "mojo/edk/system/atomic_flag.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/message_for_transit.h" +#include "mojo/edk/system/ports/port_ref.h" +#include "mojo/edk/system/watcher_set.h" + +namespace mojo { +namespace edk { + +class NodeController; + +class MessagePipeDispatcher : public Dispatcher { + public: + // Constructs a MessagePipeDispatcher permanently tied to a specific port. + // |connected| must indicate the state of the port at construction time; if + // the port is initialized with a peer, |connected| must be true. Otherwise it + // must be false. + // + // A MessagePipeDispatcher may not be transferred while in a disconnected + // state, and one can never return to a disconnected once connected. + // + // |pipe_id| is a unique identifier which can be used to track pipe endpoints + // as they're passed around. |endpoint| is either 0 or 1 and again is only + // used for tracking pipes (one side is always 0, the other is always 1.) + MessagePipeDispatcher(NodeController* node_controller, + const ports::PortRef& port, + uint64_t pipe_id, + int endpoint); + + // Fuses this pipe with |other|. Returns |true| on success or |false| on + // failure. Regardless of the return value, both dispatchers are closed by + // this call. + bool Fuse(MessagePipeDispatcher* other); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + MojoResult WriteMessage(std::unique_ptr message, + MojoWriteMessageFlags flags) override; + MojoResult ReadMessage(std::unique_ptr* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags, + bool read_any_size) override; + HandleSignalsState GetHandleSignalsState() const override; + MojoResult AddWatcherRef(const scoped_refptr& watcher, + uintptr_t context) override; + MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) override; + void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) override; + bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) override; + bool BeginTransit() override; + void CompleteTransitAndClose() override; + void CancelTransit() override; + + static scoped_refptr Deserialize( + const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles); + + private: + class PortObserverThunk; + friend class PortObserverThunk; + + ~MessagePipeDispatcher() override; + + MojoResult CloseNoLock(); + HandleSignalsState GetHandleSignalsStateNoLock() const; + void OnPortStatusChanged(); + + // These are safe to access from any thread without locking. + NodeController* const node_controller_; + const ports::PortRef port_; + const uint64_t pipe_id_; + const int endpoint_; + + // Guards access to all the fields below. + mutable base::Lock signal_lock_; + + // This is not the same is |port_transferred_|. It's only held true between + // BeginTransit() and Complete/CancelTransit(). + AtomicFlag in_transit_; + + bool port_transferred_ = false; + AtomicFlag port_closed_; + WatcherSet watchers_; + + DISALLOW_COPY_AND_ASSIGN(MessagePipeDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_ diff --git a/mojo/edk/system/message_pipe_perftest.cc b/mojo/edk/system/message_pipe_perftest.cc new file mode 100644 index 0000000000000000000000000000000000000000..9866c474dea77fc50f4a7812e90c891c4a552111 --- /dev/null +++ b/mojo/edk/system/message_pipe_perftest.cc @@ -0,0 +1,167 @@ +// Copyright 2014 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. + +#include +#include + +#include +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/strings/stringprintf.h" +#include "base/test/perf_time_logger.h" +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/edk/test/test_utils.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +class MessagePipePerfTest : public test::MojoTestBase { + public: + MessagePipePerfTest() : message_count_(0), message_size_(0) {} + + void SetUpMeasurement(int message_count, size_t message_size) { + message_count_ = message_count; + message_size_ = message_size; + payload_ = std::string(message_size, '*'); + read_buffer_.resize(message_size * 2); + } + + protected: + void WriteWaitThenRead(MojoHandle mp) { + CHECK_EQ(MojoWriteMessage(mp, payload_.data(), + static_cast(payload_.size()), nullptr, + 0, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + HandleSignalsState hss; + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + uint32_t read_buffer_size = static_cast(read_buffer_.size()); + CHECK_EQ(MojoReadMessage(mp, &read_buffer_[0], &read_buffer_size, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(read_buffer_size, static_cast(payload_.size())); + } + + void SendQuitMessage(MojoHandle mp) { + CHECK_EQ(MojoWriteMessage(mp, "", 0, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + } + + void Measure(MojoHandle mp) { + // Have one ping-pong to ensure channel being established. + WriteWaitThenRead(mp); + + std::string test_name = + base::StringPrintf("IPC_Perf_%dx_%u", message_count_, + static_cast(message_size_)); + base::PerfTimeLogger logger(test_name.c_str()); + + for (int i = 0; i < message_count_; ++i) + WriteWaitThenRead(mp); + + logger.Done(); + } + + protected: + void RunPingPongServer(MojoHandle mp) { + // This values are set to align with one at ipc_pertests.cc for comparison. + const size_t kMsgSize[5] = {12, 144, 1728, 20736, 248832}; + const int kMessageCount[5] = {50000, 50000, 50000, 12000, 1000}; + + for (size_t i = 0; i < 5; i++) { + SetUpMeasurement(kMessageCount[i], kMsgSize[i]); + Measure(mp); + } + + SendQuitMessage(mp); + } + + static int RunPingPongClient(MojoHandle mp) { + std::string buffer(1000000, '\0'); + int rv = 0; + while (true) { + // Wait for our end of the message pipe to be readable. + HandleSignalsState hss; + MojoResult result = WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss); + if (result != MOJO_RESULT_OK) { + rv = result; + break; + } + + uint32_t read_size = static_cast(buffer.size()); + CHECK_EQ(MojoReadMessage(mp, &buffer[0], + &read_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + + // Empty message indicates quit. + if (read_size == 0) + break; + + CHECK_EQ(MojoWriteMessage(mp, &buffer[0], + read_size, + nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + } + + return rv; + } + + private: + int message_count_; + size_t message_size_; + std::string payload_; + std::string read_buffer_; + std::unique_ptr perf_logger_; + + DISALLOW_COPY_AND_ASSIGN(MessagePipePerfTest); +}; + +TEST_F(MessagePipePerfTest, PingPong) { + MojoHandle server_handle, client_handle; + CreateMessagePipe(&server_handle, &client_handle); + + base::Thread client_thread("PingPongClient"); + client_thread.Start(); + client_thread.task_runner()->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult(&RunPingPongClient), client_handle)); + + RunPingPongServer(server_handle); +} + +// For each message received, sends a reply message with the same contents +// repeated twice, until the other end is closed or it receives "quitquitquit" +// (which it doesn't reply to). It'll return the number of messages received, +// not including any "quitquitquit" message, modulo 100. +DEFINE_TEST_CLIENT_WITH_PIPE(PingPongClient, MessagePipePerfTest, h) { + return RunPingPongClient(h); +} + +// Repeatedly sends messages as previous one got replied by the child. +// Waits for the child to close its end before quitting once specified +// number of messages has been sent. +TEST_F(MessagePipePerfTest, MultiprocessPingPong) { + RUN_CHILD_ON_PIPE(PingPongClient, h) + RunPingPongServer(h); + END_CHILD() +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/message_pipe_unittest.cc b/mojo/edk/system/message_pipe_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..e6f1ff643793fce31bcc145ba3e8a8fd6e8a5471 --- /dev/null +++ b/mojo/edk/system/message_pipe_unittest.cc @@ -0,0 +1,699 @@ +// Copyright 2013 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. + +#include +#include + +#include "base/memory/ref_counted.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/core.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { +namespace edk { +namespace { + +const MojoHandleSignals kAllSignals = MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED; +static const char kHelloWorld[] = "hello world"; + +class MessagePipeTest : public test::MojoTestBase { + public: + MessagePipeTest() { + CHECK_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &pipe0_, &pipe1_)); + } + + ~MessagePipeTest() override { + if (pipe0_ != MOJO_HANDLE_INVALID) + CHECK_EQ(MOJO_RESULT_OK, MojoClose(pipe0_)); + if (pipe1_ != MOJO_HANDLE_INVALID) + CHECK_EQ(MOJO_RESULT_OK, MojoClose(pipe1_)); + } + + MojoResult WriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes) { + return MojoWriteMessage(message_pipe_handle, bytes, num_bytes, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE); + } + + MojoResult ReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + bool may_discard = false) { + return MojoReadMessage(message_pipe_handle, bytes, num_bytes, nullptr, 0, + may_discard ? MOJO_READ_MESSAGE_FLAG_MAY_DISCARD : + MOJO_READ_MESSAGE_FLAG_NONE); + } + + MojoHandle pipe0_, pipe1_; + + private: + DISALLOW_COPY_AND_ASSIGN(MessagePipeTest); +}; + +using FuseMessagePipeTest = test::MojoTestBase; + +TEST_F(MessagePipeTest, WriteData) { + ASSERT_EQ(MOJO_RESULT_OK, + WriteMessage(pipe0_, kHelloWorld, sizeof(kHelloWorld))); +} + +// Tests: +// - only default flags +// - reading messages from a port +// - when there are no/one/two messages available for that port +// - with buffer size 0 (and null buffer) -- should get size +// - with too-small buffer -- should get size +// - also verify that buffers aren't modified when/where they shouldn't be +// - writing messages to a port +// - in the obvious scenarios (as above) +// - to a port that's been closed +// - writing a message to a port, closing the other (would be the source) port, +// and reading it +TEST_F(MessagePipeTest, Basic) { + int32_t buffer[2]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t buffer_size; + + // Nothing to read yet on port 0. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe0_, buffer, &buffer_size)); + ASSERT_EQ(kBufferSize, buffer_size); + ASSERT_EQ(123, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Ditto for port 1. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe1_, buffer, &buffer_size)); + + // Write from port 1 (to port 0). + buffer[0] = 789012345; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read from port 0. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe0_, buffer, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(789012345, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe0_, buffer, &buffer_size)); + + // Write two messages from port 0 (to port 1). + buffer[0] = 123456789; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, sizeof(buffer[0]))); + buffer[0] = 234567890; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, sizeof(buffer[0]))); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read from port 1 with buffer size 0 (should get the size of next message). + // Also test that giving a null buffer is okay when the buffer size is 0. + buffer_size = 0; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe1_, nullptr, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + + // Read from port 1 with buffer size 1 (too small; should get the size of next + // message). + buffer[0] = 123; + buffer[1] = 456; + buffer_size = 1; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe1_, buffer, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(123, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Read from port 1. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(123456789, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read again from port 1. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(234567890, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Read again from port 1 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe1_, buffer, &buffer_size)); + + // Write from port 0 (to port 1). + buffer[0] = 345678901; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, sizeof(buffer[0]))); + + // Close port 0. + MojoClose(pipe0_); + pipe0_ = MOJO_HANDLE_INVALID; + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); + + // Try to write from port 1 (to port 0). + buffer[0] = 456789012; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + // Read from port 1; should still get message (even though port 0 was closed). + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(345678901, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Read again from port 1 -- it should be empty (and port 0 is closed). + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + ReadMessage(pipe1_, buffer, &buffer_size)); +} + +TEST_F(MessagePipeTest, CloseWithQueuedIncomingMessages) { + int32_t buffer[1]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t buffer_size; + + // Write some messages from port 1 (to port 0). + for (int32_t i = 0; i < 5; i++) { + buffer[0] = i; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, kBufferSize)); + } + + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Port 0 shouldn't be empty. + buffer_size = 0; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe0_, nullptr, &buffer_size)); + ASSERT_EQ(kBufferSize, buffer_size); + + // Close port 0 first, which should have outstanding (incoming) messages. + MojoClose(pipe0_); + MojoClose(pipe1_); + pipe0_ = pipe1_ = MOJO_HANDLE_INVALID; +} + +TEST_F(MessagePipeTest, DiscardMode) { + int32_t buffer[2]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t buffer_size; + + // Write from port 1 (to port 0). + buffer[0] = 789012345; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read/discard from port 0 (no buffer); get size. + buffer_size = 0; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe0_, nullptr, &buffer_size, true)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, + ReadMessage(pipe0_, buffer, &buffer_size, true)); + + // Write from port 1 (to port 0). + buffer[0] = 890123456; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, + WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read from port 0 (buffer big enough). + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe0_, buffer, &buffer_size, true)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(890123456, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, + ReadMessage(pipe0_, buffer, &buffer_size, true)); + + // Write from port 1 (to port 0). + buffer[0] = 901234567; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read/discard from port 0 (buffer too small); get size. + buffer_size = 1; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe0_, buffer, &buffer_size, true)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, + ReadMessage(pipe0_, buffer, &buffer_size, true)); + + // Write from port 1 (to port 0). + buffer[0] = 123456789; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Discard from port 0. + buffer_size = 1; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe0_, nullptr, 0, true)); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, + ReadMessage(pipe0_, buffer, &buffer_size, true)); +} + +TEST_F(MessagePipeTest, BasicWaiting) { + MojoHandleSignalsState hss; + + int32_t buffer[1]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t buffer_size; + + // Always writable (until the other port is closed). Not yet readable. Peer + // not closed. + hss = GetSignalsState(pipe0_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + hss = MojoHandleSignalsState(); + + // Write from port 0 (to port 1), to make port 1 readable. + buffer[0] = 123456789; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, kBufferSize)); + + // Port 1 should already be readable now. + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + // ... and still writable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + + // Close port 0. + MojoClose(pipe0_); + pipe0_ = MOJO_HANDLE_INVALID; + + // Port 1 should be signaled with peer closed. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Port 1 should not be writable. + hss = MojoHandleSignalsState(); + + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // But it should still be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Read from port 1. + buffer[0] = 0; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size)); + ASSERT_EQ(123456789, buffer[0]); + + // Now port 1 should no longer be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +TEST_F(MessagePipeTest, InvalidMessageObjects) { + // null message + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoFreeMessage(MOJO_MESSAGE_HANDLE_INVALID)); + + // null message + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoGetMessageBuffer(MOJO_MESSAGE_HANDLE_INVALID, nullptr)); + + // Non-zero num_handles with null handles array. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoAllocMessage(0, nullptr, 1, MOJO_ALLOC_MESSAGE_FLAG_NONE, + nullptr)); +} + +TEST_F(MessagePipeTest, AllocAndFreeMessage) { + const std::string kMessage = "Hello, world."; + MojoMessageHandle message = MOJO_MESSAGE_HANDLE_INVALID; + ASSERT_EQ(MOJO_RESULT_OK, + MojoAllocMessage(static_cast(kMessage.size()), nullptr, 0, + MOJO_ALLOC_MESSAGE_FLAG_NONE, &message)); + ASSERT_NE(MOJO_MESSAGE_HANDLE_INVALID, message); + ASSERT_EQ(MOJO_RESULT_OK, MojoFreeMessage(message)); +} + +TEST_F(MessagePipeTest, WriteAndReadMessageObject) { + const std::string kMessage = "Hello, world."; + MojoMessageHandle message = MOJO_MESSAGE_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, + MojoAllocMessage(static_cast(kMessage.size()), nullptr, 0, + MOJO_ALLOC_MESSAGE_FLAG_NONE, &message)); + ASSERT_NE(MOJO_MESSAGE_HANDLE_INVALID, message); + + void* buffer = nullptr; + EXPECT_EQ(MOJO_RESULT_OK, MojoGetMessageBuffer(message, &buffer)); + ASSERT_TRUE(buffer); + memcpy(buffer, kMessage.data(), kMessage.size()); + + MojoHandle a, b; + CreateMessagePipe(&a, &b); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE)); + + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE)); + uint32_t num_bytes = 0; + uint32_t num_handles = 0; + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessageNew(b, &message, &num_bytes, nullptr, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_NE(MOJO_MESSAGE_HANDLE_INVALID, message); + EXPECT_EQ(static_cast(kMessage.size()), num_bytes); + EXPECT_EQ(0u, num_handles); + + EXPECT_EQ(MOJO_RESULT_OK, MojoGetMessageBuffer(message, &buffer)); + ASSERT_TRUE(buffer); + + EXPECT_EQ(0, strncmp(static_cast(buffer), kMessage.data(), + num_bytes)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoFreeMessage(message)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +#if !defined(OS_IOS) + +const size_t kPingPongHandlesPerIteration = 50; +const size_t kPingPongIterations = 500; + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(HandlePingPong, MessagePipeTest, h) { + // Waits for a handle to become readable and writes it back to the sender. + for (size_t i = 0; i < kPingPongIterations; i++) { + MojoHandle handles[kPingPongHandlesPerIteration]; + ReadMessageWithHandles(h, handles, kPingPongHandlesPerIteration); + WriteMessageWithHandles(h, "", handles, kPingPongHandlesPerIteration); + } + + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE)); + char msg[4]; + uint32_t num_bytes = 4; + EXPECT_EQ(MOJO_RESULT_OK, ReadMessage(h, msg, &num_bytes)); +} + +// This test is flaky: http://crbug.com/585784 +TEST_F(MessagePipeTest, DISABLED_DataPipeConsumerHandlePingPong) { + MojoHandle p, c[kPingPongHandlesPerIteration]; + for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i) { + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(nullptr, &p, &c[i])); + MojoClose(p); + } + + RUN_CHILD_ON_PIPE(HandlePingPong, h) + for (size_t i = 0; i < kPingPongIterations; i++) { + WriteMessageWithHandles(h, "", c, kPingPongHandlesPerIteration); + ReadMessageWithHandles(h, c, kPingPongHandlesPerIteration); + } + WriteMessage(h, "quit", 4); + END_CHILD() + for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i) + MojoClose(c[i]); +} + +// This test is flaky: http://crbug.com/585784 +TEST_F(MessagePipeTest, DISABLED_DataPipeProducerHandlePingPong) { + MojoHandle p[kPingPongHandlesPerIteration], c; + for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i) { + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(nullptr, &p[i], &c)); + MojoClose(c); + } + + RUN_CHILD_ON_PIPE(HandlePingPong, h) + for (size_t i = 0; i < kPingPongIterations; i++) { + WriteMessageWithHandles(h, "", p, kPingPongHandlesPerIteration); + ReadMessageWithHandles(h, p, kPingPongHandlesPerIteration); + } + WriteMessage(h, "quit", 4); + END_CHILD() + for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i) + MojoClose(p[i]); +} + +TEST_F(MessagePipeTest, SharedBufferHandlePingPong) { + MojoHandle buffers[kPingPongHandlesPerIteration]; + for (size_t i = 0; i +#include +#include +#include + +#include +#include +#include + +#include "base/bind.h" +#include "base/containers/hash_tables.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/string_split.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/edk/test/test_utils.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/simple_watcher.h" +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + + +namespace mojo { +namespace edk { +namespace { + +class MultiprocessMessagePipeTest : public test::MojoTestBase { + protected: + // Convenience class for tests which will control command-driven children. + // See the CommandDrivenClient definition below. + class CommandDrivenClientController { + public: + explicit CommandDrivenClientController(MojoHandle h) : h_(h) {} + + void Send(const std::string& command) { + WriteMessage(h_, command); + EXPECT_EQ("ok", ReadMessage(h_)); + } + + void SendHandle(const std::string& name, MojoHandle p) { + WriteMessageWithHandles(h_, "take:" + name, &p, 1); + EXPECT_EQ("ok", ReadMessage(h_)); + } + + MojoHandle RetrieveHandle(const std::string& name) { + WriteMessage(h_, "return:" + name); + MojoHandle p; + EXPECT_EQ("ok", ReadMessageWithHandles(h_, &p, 1)); + return p; + } + + void Exit() { WriteMessage(h_, "exit"); } + + private: + MojoHandle h_; + }; +}; + +class MultiprocessMessagePipeTestWithPeerSupport + : public MultiprocessMessagePipeTest, + public testing::WithParamInterface { + protected: + void SetUp() override { + test::MojoTestBase::SetUp(); + set_launch_type(GetParam()); + } +}; + +// For each message received, sends a reply message with the same contents +// repeated twice, until the other end is closed or it receives "quitquitquit" +// (which it doesn't reply to). It'll return the number of messages received, +// not including any "quitquitquit" message, modulo 100. +DEFINE_TEST_CLIENT_WITH_PIPE(EchoEcho, MultiprocessMessagePipeTest, h) { + const std::string quitquitquit("quitquitquit"); + int rv = 0; + for (;; rv = (rv + 1) % 100) { + // Wait for our end of the message pipe to be readable. + HandleSignalsState hss; + MojoResult result = WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss); + if (result != MOJO_RESULT_OK) { + // It was closed, probably. + CHECK_EQ(result, MOJO_RESULT_FAILED_PRECONDITION); + CHECK_EQ(hss.satisfied_signals, MOJO_HANDLE_SIGNAL_PEER_CLOSED); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_PEER_CLOSED); + break; + } else { + CHECK((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + CHECK((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + } + + std::string read_buffer(1000, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(h, &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + VLOG(2) << "Child got: " << read_buffer; + + if (read_buffer == quitquitquit) { + VLOG(2) << "Child quitting."; + break; + } + + std::string write_buffer = read_buffer + read_buffer; + CHECK_EQ(MojoWriteMessage(h, write_buffer.data(), + static_cast(write_buffer.size()), + nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + } + + return rv; +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, Basic) { + RUN_CHILD_ON_PIPE(EchoEcho, h) + std::string hello("hello"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, hello.data(), + static_cast(hello.size()), nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + // The child may or may not have closed its end of the message pipe and died + // (and we may or may not know it yet), so our end may or may not appear as + // writable. + EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(1000, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(h, &read_buffer[0], + &read_buffer_size, nullptr, 0, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + VLOG(2) << "Parent got: " << read_buffer; + ASSERT_EQ(hello + hello, read_buffer); + + std::string quitquitquit("quitquitquit"); + CHECK_EQ(MojoWriteMessage(h, quitquitquit.data(), + static_cast(quitquitquit.size()), + nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + END_CHILD_AND_EXPECT_EXIT_CODE(1 % 100); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, QueueMessages) { + static const size_t kNumMessages = 1001; + RUN_CHILD_ON_PIPE(EchoEcho, h) + for (size_t i = 0; i < kNumMessages; i++) { + std::string write_buffer(i, 'A' + (i % 26)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, write_buffer.data(), + static_cast(write_buffer.size()), + nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE)); + } + + for (size_t i = 0; i < kNumMessages; i++) { + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + // The child may or may not have closed its end of the message pipe and + // died (and we may or may not know it yet), so our end may or may not + // appear as writable. + ASSERT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + ASSERT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(kNumMessages * 2, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + ASSERT_EQ(MojoReadMessage(h, &read_buffer[0], + &read_buffer_size, nullptr, 0, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + + ASSERT_EQ(std::string(i * 2, 'A' + (i % 26)), read_buffer); + } + + const std::string quitquitquit("quitquitquit"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, quitquitquit.data(), + static_cast(quitquitquit.size()), + nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for it to become readable, which should fail (since we sent + // "quitquitquit"). + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); + END_CHILD_AND_EXPECT_EXIT_CODE(static_cast(kNumMessages % 100)); +} + +DEFINE_TEST_CLIENT_WITH_PIPE(CheckSharedBuffer, MultiprocessMessagePipeTest, + h) { + // Wait for the first message from our parent. + HandleSignalsState hss; + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + // In this test, the parent definitely doesn't close its end of the message + // pipe before we do. + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + // It should have a shared buffer. + std::string read_buffer(100, '\0'); + uint32_t num_bytes = static_cast(read_buffer.size()); + MojoHandle handles[10]; + uint32_t num_handlers = arraysize(handles); // Maximum number to receive + CHECK_EQ(MojoReadMessage(h, &read_buffer[0], + &num_bytes, &handles[0], + &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(num_bytes); + CHECK_EQ(read_buffer, std::string("go 1")); + CHECK_EQ(num_handlers, 1u); + + // Make a mapping. + void* buffer; + CHECK_EQ(MojoMapBuffer(handles[0], 0, 100, &buffer, + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE), + MOJO_RESULT_OK); + + // Write some stuff to the shared buffer. + static const char kHello[] = "hello"; + memcpy(buffer, kHello, sizeof(kHello)); + + // We should be able to close the dispatcher now. + MojoClose(handles[0]); + + // And send a message to signal that we've written stuff. + const std::string go2("go 2"); + CHECK_EQ(MojoWriteMessage(h, go2.data(), + static_cast(go2.size()), nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + + // Now wait for our parent to send us a message. + hss = HandleSignalsState(); + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + read_buffer = std::string(100, '\0'); + num_bytes = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(h, &read_buffer[0], &num_bytes, + nullptr, 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(num_bytes); + CHECK_EQ(read_buffer, std::string("go 3")); + + // It should have written something to the shared buffer. + static const char kWorld[] = "world!!!"; + CHECK_EQ(memcmp(buffer, kWorld, sizeof(kWorld)), 0); + + // And we're done. + + return 0; +} + +TEST_F(MultiprocessMessagePipeTest, SharedBufferPassing) { + RUN_CHILD_ON_PIPE(CheckSharedBuffer, h) + // Make a shared buffer. + MojoCreateSharedBufferOptions options; + options.struct_size = sizeof(options); + options.flags = MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE; + + MojoHandle shared_buffer; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateSharedBuffer(&options, 100, &shared_buffer)); + + // Send the shared buffer. + const std::string go1("go 1"); + + MojoHandle duplicated_shared_buffer; + ASSERT_EQ(MOJO_RESULT_OK, + MojoDuplicateBufferHandle( + shared_buffer, + nullptr, + &duplicated_shared_buffer)); + MojoHandle handles[1]; + handles[0] = duplicated_shared_buffer; + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, &go1[0], + static_cast(go1.size()), &handles[0], + arraysize(handles), + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for a message from the child. + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(100, '\0'); + uint32_t num_bytes = static_cast(read_buffer.size()); + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(h, &read_buffer[0], + &num_bytes, nullptr, 0, + MOJO_READ_MESSAGE_FLAG_NONE)); + read_buffer.resize(num_bytes); + ASSERT_EQ(std::string("go 2"), read_buffer); + + // After we get it, the child should have written something to the shared + // buffer. + static const char kHello[] = "hello"; + void* buffer; + CHECK_EQ(MojoMapBuffer(shared_buffer, 0, 100, &buffer, + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE), + MOJO_RESULT_OK); + ASSERT_EQ(0, memcmp(buffer, kHello, sizeof(kHello))); + + // Now we'll write some stuff to the shared buffer. + static const char kWorld[] = "world!!!"; + memcpy(buffer, kWorld, sizeof(kWorld)); + + // And send a message to signal that we've written stuff. + const std::string go3("go 3"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, &go3[0], + static_cast(go3.size()), nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for |h| to become readable, which should fail. + hss = HandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); + END_CHILD() +} + +DEFINE_TEST_CLIENT_WITH_PIPE(CheckPlatformHandleFile, + MultiprocessMessagePipeTest, h) { + HandleSignalsState hss; + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + std::string read_buffer(100, '\0'); + uint32_t num_bytes = static_cast(read_buffer.size()); + MojoHandle handles[255]; // Maximum number to receive. + uint32_t num_handlers = arraysize(handles); + + CHECK_EQ(MojoReadMessage(h, &read_buffer[0], + &num_bytes, &handles[0], + &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + + read_buffer.resize(num_bytes); + char hello[32]; + int num_handles = 0; + sscanf(read_buffer.c_str(), "%s %d", hello, &num_handles); + CHECK_EQ(std::string("hello"), std::string(hello)); + CHECK_GT(num_handles, 0); + + for (int i = 0; i < num_handles; ++i) { + ScopedPlatformHandle h; + CHECK_EQ(PassWrappedPlatformHandle(handles[i], &h), MOJO_RESULT_OK); + CHECK(h.is_valid()); + MojoClose(handles[i]); + + base::ScopedFILE fp(test::FILEFromPlatformHandle(std::move(h), "r")); + CHECK(fp); + std::string fread_buffer(100, '\0'); + size_t bytes_read = + fread(&fread_buffer[0], 1, fread_buffer.size(), fp.get()); + fread_buffer.resize(bytes_read); + CHECK_EQ(fread_buffer, "world"); + } + + return 0; +} + +class MultiprocessMessagePipeTestWithPipeCount + : public MultiprocessMessagePipeTest, + public testing::WithParamInterface {}; + +TEST_P(MultiprocessMessagePipeTestWithPipeCount, PlatformHandlePassing) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + RUN_CHILD_ON_PIPE(CheckPlatformHandleFile, h) + std::vector handles; + + size_t pipe_count = GetParam(); + for (size_t i = 0; i < pipe_count; ++i) { + base::FilePath unused; + base::ScopedFILE fp( + CreateAndOpenTemporaryFileInDir(temp_dir.GetPath(), &unused)); + const std::string world("world"); + CHECK_EQ(fwrite(&world[0], 1, world.size(), fp.get()), world.size()); + fflush(fp.get()); + rewind(fp.get()); + MojoHandle handle; + ASSERT_EQ( + CreatePlatformHandleWrapper( + ScopedPlatformHandle(test::PlatformHandleFromFILE(std::move(fp))), + &handle), + MOJO_RESULT_OK); + handles.push_back(handle); + } + + char message[128]; + snprintf(message, sizeof(message), "hello %d", + static_cast(pipe_count)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, message, + static_cast(strlen(message)), + &handles[0], + static_cast(handles.size()), + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for it to become readable, which should fail. + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); + END_CHILD() +} + +// Android multi-process tests are not executing the new process. This is flaky. +#if !defined(OS_ANDROID) +INSTANTIATE_TEST_CASE_P(PipeCount, + MultiprocessMessagePipeTestWithPipeCount, + // TODO(rockot): Re-enable the 140-pipe case when + // ChannelPosix has support for sending lots of handles. + testing::Values(1u, 128u /*, 140u*/)); +#endif + +DEFINE_TEST_CLIENT_WITH_PIPE(CheckMessagePipe, MultiprocessMessagePipeTest, h) { + // Wait for the first message from our parent. + HandleSignalsState hss; + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + // In this test, the parent definitely doesn't close its end of the message + // pipe before we do. + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + // It should have a message pipe. + MojoHandle handles[10]; + uint32_t num_handlers = arraysize(handles); + CHECK_EQ(MojoReadMessage(h, nullptr, + nullptr, &handles[0], + &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(num_handlers, 1u); + + // Read data from the received message pipe. + CHECK_EQ(WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + std::string read_buffer(100, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(handles[0], &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + CHECK_EQ(read_buffer, std::string("hello")); + + // Now write some data into the message pipe. + std::string write_buffer = "world"; + CHECK_EQ(MojoWriteMessage(handles[0], write_buffer.data(), + static_cast(write_buffer.size()), nullptr, + 0u, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + MojoClose(handles[0]); + return 0; +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, MessagePipePassing) { + RUN_CHILD_ON_PIPE(CheckMessagePipe, h) + MojoCreateSharedBufferOptions options; + options.struct_size = sizeof(options); + options.flags = MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE; + + MojoHandle mp1, mp2; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateMessagePipe(nullptr, &mp1, &mp2)); + + // Write a string into one end of the new message pipe and send the other + // end. + const std::string hello("hello"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(mp1, &hello[0], + static_cast(hello.size()), nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, nullptr, 0, &mp2, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for a message from the child. + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(100, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(mp1, &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + CHECK_EQ(read_buffer, std::string("world")); + + MojoClose(mp1); + END_CHILD() +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, MessagePipeTwoPassing) { + RUN_CHILD_ON_PIPE(CheckMessagePipe, h) + MojoHandle mp1, mp2; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateMessagePipe(nullptr, &mp2, &mp1)); + + // Write a string into one end of the new message pipe and send the other + // end. + const std::string hello("hello"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(mp1, &hello[0], + static_cast(hello.size()), nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, nullptr, 0u, &mp2, 1u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for a message from the child. + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(100, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(mp1, &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + CHECK_EQ(read_buffer, std::string("world")); + END_CHILD(); +} + +DEFINE_TEST_CLIENT_WITH_PIPE(DataPipeConsumer, MultiprocessMessagePipeTest, h) { + // Wait for the first message from our parent. + HandleSignalsState hss; + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + // In this test, the parent definitely doesn't close its end of the message + // pipe before we do. + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + // It should have a message pipe. + MojoHandle handles[10]; + uint32_t num_handlers = arraysize(handles); + CHECK_EQ(MojoReadMessage(h, nullptr, + nullptr, &handles[0], + &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(num_handlers, 1u); + + // Read data from the received message pipe. + CHECK_EQ(WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + std::string read_buffer(100, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(handles[0], &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + CHECK_EQ(read_buffer, std::string("hello")); + + // Now write some data into the message pipe. + std::string write_buffer = "world"; + CHECK_EQ(MojoWriteMessage(handles[0], write_buffer.data(), + static_cast(write_buffer.size()), + nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + MojoClose(handles[0]); + return 0; +} + +TEST_F(MultiprocessMessagePipeTest, DataPipeConsumer) { + RUN_CHILD_ON_PIPE(DataPipeConsumer, h) + MojoCreateSharedBufferOptions options; + options.struct_size = sizeof(options); + options.flags = MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE; + + MojoHandle mp1, mp2; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateMessagePipe(nullptr, &mp2, &mp1)); + + // Write a string into one end of the new message pipe and send the other + // end. + const std::string hello("hello"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(mp1, &hello[0], + static_cast(hello.size()), nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, nullptr, 0, &mp2, 1u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for a message from the child. + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(100, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(mp1, &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + CHECK_EQ(read_buffer, std::string("world")); + + MojoClose(mp1); + END_CHILD(); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, CreateMessagePipe) { + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + VerifyTransmission(p0, p1, std::string(10 * 1024 * 1024, 'a')); + VerifyTransmission(p1, p0, std::string(10 * 1024 * 1024, 'e')); + + CloseHandle(p0); + CloseHandle(p1); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, PassMessagePipeLocal) { + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + VerifyTransmission(p0, p1, "testing testing"); + VerifyTransmission(p1, p0, "one two three"); + + MojoHandle p2, p3; + + CreateMessagePipe(&p2, &p3); + VerifyTransmission(p2, p3, "testing testing"); + VerifyTransmission(p3, p2, "one two three"); + + // Pass p2 over p0 to p1. + const std::string message = "ceci n'est pas une pipe"; + WriteMessageWithHandles(p0, message, &p2, 1); + EXPECT_EQ(message, ReadMessageWithHandles(p1, &p2, 1)); + + CloseHandle(p0); + CloseHandle(p1); + + // Verify that the received handle (now in p2) still works. + VerifyTransmission(p2, p3, "Easy come, easy go; will you let me go?"); + VerifyTransmission(p3, p2, "Bismillah! NO! We will not let you go!"); + + CloseHandle(p2); + CloseHandle(p3); +} + +// Echos the primordial channel until "exit". +DEFINE_TEST_CLIENT_WITH_PIPE(ChannelEchoClient, MultiprocessMessagePipeTest, + h) { + for (;;) { + std::string message = ReadMessage(h); + if (message == "exit") + break; + WriteMessage(h, message); + } + return 0; +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, MultiprocessChannelPipe) { + RUN_CHILD_ON_PIPE(ChannelEchoClient, h) + VerifyEcho(h, "in an interstellar burst"); + VerifyEcho(h, "i am back to save the universe"); + VerifyEcho(h, std::string(10 * 1024 * 1024, 'o')); + + WriteMessage(h, "exit"); + END_CHILD() +} + +// Receives a pipe handle from the primordial channel and echos on it until +// "exit". Used to test simple pipe transfer across processes via channels. +DEFINE_TEST_CLIENT_WITH_PIPE(EchoServiceClient, MultiprocessMessagePipeTest, + h) { + MojoHandle p; + ReadMessageWithHandles(h, &p, 1); + for (;;) { + std::string message = ReadMessage(p); + if (message == "exit") + break; + WriteMessage(p, message); + } + return 0; +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, + PassMessagePipeCrossProcess) { + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + RUN_CHILD_ON_PIPE(EchoServiceClient, h) + + // Pass one end of the pipe to the other process. + WriteMessageWithHandles(h, "here take this", &p1, 1); + + VerifyEcho(p0, "and you may ask yourself"); + VerifyEcho(p0, "where does that highway go?"); + VerifyEcho(p0, std::string(20 * 1024 * 1024, 'i')); + + WriteMessage(p0, "exit"); + END_CHILD() + CloseHandle(p0); +} + +// Receives a pipe handle from the primordial channel and reads new handles +// from it. Each read handle establishes a new echo channel. +DEFINE_TEST_CLIENT_WITH_PIPE(EchoServiceFactoryClient, + MultiprocessMessagePipeTest, h) { + MojoHandle p; + ReadMessageWithHandles(h, &p, 1); + + std::vector handles(2); + handles[0] = Handle(h); + handles[1] = Handle(p); + std::vector signals(2, MOJO_HANDLE_SIGNAL_READABLE); + for (;;) { + size_t index; + CHECK_EQ( + mojo::WaitMany(handles.data(), signals.data(), handles.size(), &index), + MOJO_RESULT_OK); + DCHECK_LE(index, handles.size()); + if (index == 0) { + // If data is available on the first pipe, it should be an exit command. + EXPECT_EQ(std::string("exit"), ReadMessage(h)); + break; + } else if (index == 1) { + // If the second pipe, it should be a new handle requesting echo service. + MojoHandle echo_request; + ReadMessageWithHandles(p, &echo_request, 1); + handles.push_back(Handle(echo_request)); + signals.push_back(MOJO_HANDLE_SIGNAL_READABLE); + } else { + // Otherwise it was one of our established echo pipes. Echo! + WriteMessage(handles[index].value(), ReadMessage(handles[index].value())); + } + } + + for (size_t i = 1; i < handles.size(); ++i) + CloseHandle(handles[i].value()); + + return 0; +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, + PassMoarMessagePipesCrossProcess) { + MojoHandle echo_factory_proxy, echo_factory_request; + CreateMessagePipe(&echo_factory_proxy, &echo_factory_request); + + MojoHandle echo_proxy_a, echo_request_a; + CreateMessagePipe(&echo_proxy_a, &echo_request_a); + + MojoHandle echo_proxy_b, echo_request_b; + CreateMessagePipe(&echo_proxy_b, &echo_request_b); + + MojoHandle echo_proxy_c, echo_request_c; + CreateMessagePipe(&echo_proxy_c, &echo_request_c); + + RUN_CHILD_ON_PIPE(EchoServiceFactoryClient, h) + WriteMessageWithHandles( + h, "gief factory naow plz", &echo_factory_request, 1); + + WriteMessageWithHandles(echo_factory_proxy, "give me an echo service plz!", + &echo_request_a, 1); + WriteMessageWithHandles(echo_factory_proxy, "give me one too!", + &echo_request_b, 1); + + VerifyEcho(echo_proxy_a, "i came here for an argument"); + VerifyEcho(echo_proxy_a, "shut your festering gob"); + VerifyEcho(echo_proxy_a, "mumble mumble mumble"); + + VerifyEcho(echo_proxy_b, "wubalubadubdub"); + VerifyEcho(echo_proxy_b, "wubalubadubdub"); + + WriteMessageWithHandles(echo_factory_proxy, "hook me up also thanks", + &echo_request_c, 1); + + VerifyEcho(echo_proxy_a, "the frobinators taste like frobinators"); + VerifyEcho(echo_proxy_b, "beep bop boop"); + VerifyEcho(echo_proxy_c, "zzzzzzzzzzzzzzzzzzzzzzzzzz"); + + WriteMessage(h, "exit"); + END_CHILD() + + CloseHandle(echo_factory_proxy); + CloseHandle(echo_proxy_a); + CloseHandle(echo_proxy_b); + CloseHandle(echo_proxy_c); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, + ChannelPipesWithMultipleChildren) { + RUN_CHILD_ON_PIPE(ChannelEchoClient, a) + RUN_CHILD_ON_PIPE(ChannelEchoClient, b) + VerifyEcho(a, "hello child 0"); + VerifyEcho(b, "hello child 1"); + + WriteMessage(a, "exit"); + WriteMessage(b, "exit"); + END_CHILD() + END_CHILD() +} + +// Reads and turns a pipe handle some number of times to create lots of +// transient proxies. +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(PingPongPipeClient, + MultiprocessMessagePipeTest, h) { + const size_t kNumBounces = 50; + MojoHandle p0, p1; + ReadMessageWithHandles(h, &p0, 1); + ReadMessageWithHandles(h, &p1, 1); + for (size_t i = 0; i < kNumBounces; ++i) { + WriteMessageWithHandles(h, "", &p1, 1); + ReadMessageWithHandles(h, &p1, 1); + } + WriteMessageWithHandles(h, "", &p0, 1); + WriteMessage(p1, "bye"); + MojoClose(p1); + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, PingPongPipe) { + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + + RUN_CHILD_ON_PIPE(PingPongPipeClient, h) + const size_t kNumBounces = 50; + WriteMessageWithHandles(h, "", &p0, 1); + WriteMessageWithHandles(h, "", &p1, 1); + for (size_t i = 0; i < kNumBounces; ++i) { + ReadMessageWithHandles(h, &p1, 1); + WriteMessageWithHandles(h, "", &p1, 1); + } + ReadMessageWithHandles(h, &p0, 1); + WriteMessage(h, "quit"); + END_CHILD() + + EXPECT_EQ("bye", ReadMessage(p0)); + + // We should still be able to observe peer closure from the other end. + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(p0, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +// Parses commands from the parent pipe and does whatever it's asked to do. +DEFINE_TEST_CLIENT_WITH_PIPE(CommandDrivenClient, MultiprocessMessagePipeTest, + h) { + base::hash_map named_pipes; + for (;;) { + MojoHandle p; + auto parts = base::SplitString(ReadMessageWithOptionalHandle(h, &p), ":", + base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); + CHECK(!parts.empty()); + std::string command = parts[0]; + if (command == "take") { + // Take a pipe. + CHECK_EQ(parts.size(), 2u); + CHECK_NE(p, MOJO_HANDLE_INVALID); + named_pipes[parts[1]] = p; + WriteMessage(h, "ok"); + } else if (command == "return") { + // Return a pipe. + CHECK_EQ(parts.size(), 2u); + CHECK_EQ(p, MOJO_HANDLE_INVALID); + p = named_pipes[parts[1]]; + CHECK_NE(p, MOJO_HANDLE_INVALID); + named_pipes.erase(parts[1]); + WriteMessageWithHandles(h, "ok", &p, 1); + } else if (command == "say") { + // Say something to a named pipe. + CHECK_EQ(parts.size(), 3u); + CHECK_EQ(p, MOJO_HANDLE_INVALID); + p = named_pipes[parts[1]]; + CHECK_NE(p, MOJO_HANDLE_INVALID); + CHECK(!parts[2].empty()); + WriteMessage(p, parts[2]); + WriteMessage(h, "ok"); + } else if (command == "hear") { + // Expect to read something from a named pipe. + CHECK_EQ(parts.size(), 3u); + CHECK_EQ(p, MOJO_HANDLE_INVALID); + p = named_pipes[parts[1]]; + CHECK_NE(p, MOJO_HANDLE_INVALID); + CHECK(!parts[2].empty()); + CHECK_EQ(parts[2], ReadMessage(p)); + WriteMessage(h, "ok"); + } else if (command == "pass") { + // Pass one named pipe over another named pipe. + CHECK_EQ(parts.size(), 3u); + CHECK_EQ(p, MOJO_HANDLE_INVALID); + p = named_pipes[parts[1]]; + MojoHandle carrier = named_pipes[parts[2]]; + CHECK_NE(p, MOJO_HANDLE_INVALID); + CHECK_NE(carrier, MOJO_HANDLE_INVALID); + named_pipes.erase(parts[1]); + WriteMessageWithHandles(carrier, "got a pipe for ya", &p, 1); + WriteMessage(h, "ok"); + } else if (command == "catch") { + // Expect to receive one named pipe from another named pipe. + CHECK_EQ(parts.size(), 3u); + CHECK_EQ(p, MOJO_HANDLE_INVALID); + MojoHandle carrier = named_pipes[parts[2]]; + CHECK_NE(carrier, MOJO_HANDLE_INVALID); + ReadMessageWithHandles(carrier, &p, 1); + CHECK_NE(p, MOJO_HANDLE_INVALID); + named_pipes[parts[1]] = p; + WriteMessage(h, "ok"); + } else if (command == "exit") { + CHECK_EQ(parts.size(), 1u); + break; + } + } + + for (auto& pipe : named_pipes) + CloseHandle(pipe.second); + + return 0; +} + +TEST_F(MultiprocessMessagePipeTest, ChildToChildPipes) { + RUN_CHILD_ON_PIPE(CommandDrivenClient, h0) + RUN_CHILD_ON_PIPE(CommandDrivenClient, h1) + CommandDrivenClientController a(h0); + CommandDrivenClientController b(h1); + + // Create a pipe and pass each end to a different client. + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + a.SendHandle("x", p0); + b.SendHandle("y", p1); + + // Make sure they can talk. + a.Send("say:x:hello"); + b.Send("hear:y:hello"); + + b.Send("say:y:i love multiprocess pipes!"); + a.Send("hear:x:i love multiprocess pipes!"); + + a.Exit(); + b.Exit(); + END_CHILD() + END_CHILD() +} + +TEST_F(MultiprocessMessagePipeTest, MoreChildToChildPipes) { + RUN_CHILD_ON_PIPE(CommandDrivenClient, h0) + RUN_CHILD_ON_PIPE(CommandDrivenClient, h1) + RUN_CHILD_ON_PIPE(CommandDrivenClient, h2) + RUN_CHILD_ON_PIPE(CommandDrivenClient, h3) + CommandDrivenClientController a(h0), b(h1), c(h2), d(h3); + + // Connect a to b and c to d + + MojoHandle p0, p1; + + CreateMessagePipe(&p0, &p1); + a.SendHandle("b_pipe", p0); + b.SendHandle("a_pipe", p1); + + MojoHandle p2, p3; + + CreateMessagePipe(&p2, &p3); + c.SendHandle("d_pipe", p2); + d.SendHandle("c_pipe", p3); + + // Connect b to c via a and d + MojoHandle p4, p5; + CreateMessagePipe(&p4, &p5); + a.SendHandle("d_pipe", p4); + d.SendHandle("a_pipe", p5); + + // Have |a| pass its new |d|-pipe to |b|. It will eventually connect + // to |c|. + a.Send("pass:d_pipe:b_pipe"); + b.Send("catch:c_pipe:a_pipe"); + + // Have |d| pass its new |a|-pipe to |c|. It will now be connected to + // |b|. + d.Send("pass:a_pipe:c_pipe"); + c.Send("catch:b_pipe:d_pipe"); + + // Make sure b and c and talk. + b.Send("say:c_pipe:it's a beautiful day"); + c.Send("hear:b_pipe:it's a beautiful day"); + + // Create x and y and have b and c exchange them. + MojoHandle x, y; + CreateMessagePipe(&x, &y); + b.SendHandle("x", x); + c.SendHandle("y", y); + b.Send("pass:x:c_pipe"); + c.Send("pass:y:b_pipe"); + b.Send("catch:y:c_pipe"); + c.Send("catch:x:b_pipe"); + + // Make sure the pipe still works in both directions. + b.Send("say:y:hello"); + c.Send("hear:x:hello"); + c.Send("say:x:goodbye"); + b.Send("hear:y:goodbye"); + + // Take both pipes back. + y = c.RetrieveHandle("x"); + x = b.RetrieveHandle("y"); + + VerifyTransmission(x, y, "still works"); + VerifyTransmission(y, x, "in both directions"); + + CloseHandle(x); + CloseHandle(y); + + a.Exit(); + b.Exit(); + c.Exit(); + d.Exit(); + END_CHILD() + END_CHILD() + END_CHILD() + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceivePipeWithClosedPeer, + MultiprocessMessagePipeTest, h) { + MojoHandle p; + EXPECT_EQ("foo", ReadMessageWithHandles(h, &p, 1)); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, SendPipeThenClosePeer) { + RUN_CHILD_ON_PIPE(ReceivePipeWithClosedPeer, h) + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + // Send |a| and immediately close |b|. The child should observe closure. + WriteMessageWithHandles(h, "foo", &a, 1); + MojoClose(b); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(SendOtherChildPipeWithClosedPeer, + MultiprocessMessagePipeTest, h) { + // Create a new pipe and send one end to the parent, who will connect it to + // a client running ReceivePipeWithClosedPeerFromOtherChild. + MojoHandle application_proxy, application_request; + CreateMessagePipe(&application_proxy, &application_request); + WriteMessageWithHandles(h, "c2a plz", &application_request, 1); + + // Create another pipe and send one end to the remote "application". + MojoHandle service_proxy, service_request; + CreateMessagePipe(&service_proxy, &service_request); + WriteMessageWithHandles(application_proxy, "c2s lol", &service_request, 1); + + // Immediately close the service proxy. The "application" should detect this. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(service_proxy)); + + // Wait for quit. + EXPECT_EQ("quit", ReadMessage(h)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceivePipeWithClosedPeerFromOtherChild, + MultiprocessMessagePipeTest, h) { + // Receive a pipe from the parent. This is akin to an "application request". + MojoHandle application_client; + EXPECT_EQ("c2a", ReadMessageWithHandles(h, &application_client, 1)); + + // Receive a pipe from the "application" "client". + MojoHandle service_client; + EXPECT_EQ("c2s lol", + ReadMessageWithHandles(application_client, &service_client, 1)); + + // Wait for the service client to signal closure. + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(service_client, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(service_client)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(application_client)); +} + +#if defined(OS_ANDROID) +// Android multi-process tests are not executing the new process. This is flaky. +#define MAYBE_SendPipeWithClosedPeerBetweenChildren \ + DISABLED_SendPipeWithClosedPeerBetweenChildren +#else +#define MAYBE_SendPipeWithClosedPeerBetweenChildren \ + SendPipeWithClosedPeerBetweenChildren +#endif +TEST_F(MultiprocessMessagePipeTest, + MAYBE_SendPipeWithClosedPeerBetweenChildren) { + RUN_CHILD_ON_PIPE(SendOtherChildPipeWithClosedPeer, kid_a) + RUN_CHILD_ON_PIPE(ReceivePipeWithClosedPeerFromOtherChild, kid_b) + // Receive an "application request" from the first child and forward it + // to the second child. + MojoHandle application_request; + EXPECT_EQ("c2a plz", + ReadMessageWithHandles(kid_a, &application_request, 1)); + + WriteMessageWithHandles(kid_b, "c2a", &application_request, 1); + END_CHILD() + + WriteMessage(kid_a, "quit"); + END_CHILD() +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, SendClosePeerSend) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + // Send |a| over |c|, immediately close |b|, then send |a| back over |d|. + WriteMessageWithHandles(c, "foo", &a, 1); + EXPECT_EQ("foo", ReadMessageWithHandles(d, &a, 1)); + WriteMessageWithHandles(d, "bar", &a, 1); + EXPECT_EQ("bar", ReadMessageWithHandles(c, &a, 1)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + + // We should be able to detect peer closure on |a|. + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(a, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(WriteCloseSendPeerClient, + MultiprocessMessagePipeTest, h) { + MojoHandle pipe[2]; + EXPECT_EQ("foo", ReadMessageWithHandles(h, pipe, 2)); + + // Write some messages to the first endpoint and then close it. + WriteMessage(pipe[0], "baz"); + WriteMessage(pipe[0], "qux"); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe[0])); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + // Pass the orphaned endpoint over another pipe before passing it back to + // the parent, just for some extra proxying goodness. + WriteMessageWithHandles(c, "foo", &pipe[1], 1); + EXPECT_EQ("foo", ReadMessageWithHandles(d, &pipe[1], 1)); + + // And finally pass it back to the parent. + WriteMessageWithHandles(h, "bar", &pipe[1], 1); + + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, WriteCloseSendPeer) { + MojoHandle pipe[2]; + CreateMessagePipe(&pipe[0], &pipe[1]); + + RUN_CHILD_ON_PIPE(WriteCloseSendPeerClient, h) + // Pass the pipe to the child. + WriteMessageWithHandles(h, "foo", pipe, 2); + + // Read back an endpoint which should have messages on it. + MojoHandle p; + EXPECT_EQ("bar", ReadMessageWithHandles(h, &p, 1)); + + EXPECT_EQ("baz", ReadMessage(p)); + EXPECT_EQ("qux", ReadMessage(p)); + + // Expect to have peer closure signaled. + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + WriteMessage(h, "quit"); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MessagePipeStatusChangeInTransitClient, + MultiprocessMessagePipeTest, parent) { + // This test verifies that peer closure is detectable through various + // mechanisms when it races with handle transfer. + MojoHandle handles[4]; + EXPECT_EQ("o_O", ReadMessageWithHandles(parent, handles, 4)); + + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + base::MessageLoop message_loop; + + // Wait on handle 1 using a SimpleWatcher. + { + base::RunLoop run_loop; + SimpleWatcher watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + watcher.Watch(Handle(handles[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, + base::Bind( + [](base::RunLoop* loop, MojoResult result) { + EXPECT_EQ(MOJO_RESULT_OK, result); + loop->Quit(); + }, + &run_loop)); + run_loop.Run(); + } + + // Wait on handle 2 by polling with MojoReadMessage. + MojoResult result; + do { + result = MojoReadMessage(handles[2], nullptr, nullptr, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE); + } while (result == MOJO_RESULT_SHOULD_WAIT); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + + // Wait on handle 3 by polling with MojoWriteMessage. + do { + result = MojoWriteMessage(handles[3], nullptr, 0, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE); + } while (result == MOJO_RESULT_OK); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + + for (size_t i = 0; i < 4; ++i) + CloseHandle(handles[i]); +} + +TEST_F(MultiprocessMessagePipeTest, MessagePipeStatusChangeInTransit) { + MojoHandle local_handles[4]; + MojoHandle sent_handles[4]; + for (size_t i = 0; i < 4; ++i) + CreateMessagePipe(&local_handles[i], &sent_handles[i]); + + RUN_CHILD_ON_PIPE(MessagePipeStatusChangeInTransitClient, child) + // Send 4 handles and let their transfer race with their peers' closure. + WriteMessageWithHandles(child, "o_O", sent_handles, 4); + for (size_t i = 0; i < 4; ++i) + CloseHandle(local_handles[i]); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(BadMessageClient, MultiprocessMessagePipeTest, + parent) { + MojoHandle pipe; + EXPECT_EQ("hi", ReadMessageWithHandles(parent, &pipe, 1)); + WriteMessage(pipe, "derp"); + EXPECT_EQ("bye", ReadMessage(parent)); +} + +void OnProcessError(std::string* out_error, const std::string& error) { + *out_error = error; +} + +TEST_F(MultiprocessMessagePipeTest, NotifyBadMessage) { + const std::string kFirstErrorMessage = "everything is terrible!"; + const std::string kSecondErrorMessage = "not the bits you're looking for"; + + std::string first_process_error; + std::string second_process_error; + + set_process_error_callback(base::Bind(&OnProcessError, &first_process_error)); + RUN_CHILD_ON_PIPE(BadMessageClient, child1) + set_process_error_callback(base::Bind(&OnProcessError, + &second_process_error)); + RUN_CHILD_ON_PIPE(BadMessageClient, child2) + MojoHandle a, b, c, d; + CreateMessagePipe(&a, &b); + CreateMessagePipe(&c, &d); + WriteMessageWithHandles(child1, "hi", &b, 1); + WriteMessageWithHandles(child2, "hi", &d, 1); + + // Read a message from the pipe we sent to child1 and flag it as bad. + ASSERT_EQ(MOJO_RESULT_OK, WaitForSignals(a, MOJO_HANDLE_SIGNAL_READABLE)); + uint32_t num_bytes = 0; + MojoMessageHandle message; + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessageNew(a, &message, &num_bytes, nullptr, 0, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoNotifyBadMessage(message, kFirstErrorMessage.data(), + kFirstErrorMessage.size())); + EXPECT_EQ(MOJO_RESULT_OK, MojoFreeMessage(message)); + + // Read a message from the pipe we sent to child2 and flag it as bad. + ASSERT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessageNew(c, &message, &num_bytes, nullptr, 0, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoNotifyBadMessage(message, kSecondErrorMessage.data(), + kSecondErrorMessage.size())); + EXPECT_EQ(MOJO_RESULT_OK, MojoFreeMessage(message)); + + WriteMessage(child2, "bye"); + END_CHILD(); + + WriteMessage(child1, "bye"); + END_CHILD() + + // The error messages should match the processes which triggered them. + EXPECT_NE(std::string::npos, first_process_error.find(kFirstErrorMessage)); + EXPECT_NE(std::string::npos, second_process_error.find(kSecondErrorMessage)); +} +INSTANTIATE_TEST_CASE_P( + , + MultiprocessMessagePipeTestWithPeerSupport, + testing::Values(test::MojoTestBase::LaunchType::CHILD, + test::MojoTestBase::LaunchType::PEER, + test::MojoTestBase::LaunchType::NAMED_CHILD, + test::MojoTestBase::LaunchType::NAMED_PEER)); +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/node_channel.cc b/mojo/edk/system/node_channel.cc new file mode 100644 index 0000000000000000000000000000000000000000..b0f770d9076fe0f4111afdee33d0aa2785a66ddc --- /dev/null +++ b/mojo/edk/system/node_channel.cc @@ -0,0 +1,905 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/node_channel.h" + +#include +#include +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "mojo/edk/system/channel.h" +#include "mojo/edk/system/request_context.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "mojo/edk/system/mach_port_relay.h" +#endif + +namespace mojo { +namespace edk { + +namespace { + +template +T Align(T t) { + const auto k = kChannelMessageAlignment; + return t + (k - (t % k)) % k; +} + +// NOTE: Please ONLY append messages to the end of this enum. +enum class MessageType : uint32_t { + ACCEPT_CHILD, + ACCEPT_PARENT, + ADD_BROKER_CLIENT, + BROKER_CLIENT_ADDED, + ACCEPT_BROKER_CLIENT, + PORTS_MESSAGE, + REQUEST_PORT_MERGE, + REQUEST_INTRODUCTION, + INTRODUCE, +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + RELAY_PORTS_MESSAGE, +#endif + BROADCAST, +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + PORTS_MESSAGE_FROM_RELAY, +#endif + ACCEPT_PEER, +}; + +struct Header { + MessageType type; + uint32_t padding; +}; + +static_assert(IsAlignedForChannelMessage(sizeof(Header)), + "Invalid header size."); + +struct AcceptChildData { + ports::NodeName parent_name; + ports::NodeName token; +}; + +struct AcceptParentData { + ports::NodeName token; + ports::NodeName child_name; +}; + +struct AcceptPeerData { + ports::NodeName token; + ports::NodeName peer_name; + ports::PortName port_name; +}; + +// This message may include a process handle on plaforms that require it. +struct AddBrokerClientData { + ports::NodeName client_name; +#if !defined(OS_WIN) + uint32_t process_handle; + uint32_t padding; +#endif +}; + +#if !defined(OS_WIN) +static_assert(sizeof(base::ProcessHandle) == sizeof(uint32_t), + "Unexpected pid size"); +static_assert(sizeof(AddBrokerClientData) % kChannelMessageAlignment == 0, + "Invalid AddBrokerClientData size."); +#endif + +// This data is followed by a platform channel handle to the broker. +struct BrokerClientAddedData { + ports::NodeName client_name; +}; + +// This data may be followed by a platform channel handle to the broker. If not, +// then the parent is the broker and its channel should be used as such. +struct AcceptBrokerClientData { + ports::NodeName broker_name; +}; + +// This is followed by arbitrary payload data which is interpreted as a token +// string for port location. +struct RequestPortMergeData { + ports::PortName connector_port_name; +}; + +// Used for both REQUEST_INTRODUCTION and INTRODUCE. +// +// For INTRODUCE the message also includes a valid platform handle for a channel +// the receiver may use to communicate with the named node directly, or an +// invalid platform handle if the node is unknown to the sender or otherwise +// cannot be introduced. +struct IntroductionData { + ports::NodeName name; +}; + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) +// This struct is followed by the full payload of a message to be relayed. +struct RelayPortsMessageData { + ports::NodeName destination; +}; + +// This struct is followed by the full payload of a relayed message. +struct PortsMessageFromRelayData { + ports::NodeName source; +}; +#endif + +template +Channel::MessagePtr CreateMessage(MessageType type, + size_t payload_size, + size_t num_handles, + DataType** out_data) { + Channel::MessagePtr message( + new Channel::Message(sizeof(Header) + payload_size, num_handles)); + Header* header = reinterpret_cast(message->mutable_payload()); + header->type = type; + header->padding = 0; + *out_data = reinterpret_cast(&header[1]); + return message; +} + +template +bool GetMessagePayload(const void* bytes, + size_t num_bytes, + DataType** out_data) { + static_assert(sizeof(DataType) > 0, "DataType must have non-zero size."); + if (num_bytes < sizeof(Header) + sizeof(DataType)) + return false; + *out_data = reinterpret_cast( + static_cast(bytes) + sizeof(Header)); + return true; +} + +} // namespace + +// static +scoped_refptr NodeChannel::Create( + Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner, + const ProcessErrorCallback& process_error_callback) { +#if defined(OS_NACL_SFI) + LOG(FATAL) << "Multi-process not yet supported on NaCl-SFI"; + return nullptr; +#else + return new NodeChannel(delegate, std::move(connection_params), io_task_runner, + process_error_callback); +#endif +} + +// static +Channel::MessagePtr NodeChannel::CreatePortsMessage(size_t payload_size, + void** payload, + size_t num_handles) { + return CreateMessage(MessageType::PORTS_MESSAGE, payload_size, num_handles, + payload); +} + +// static +void NodeChannel::GetPortsMessageData(Channel::Message* message, + void** data, + size_t* num_data_bytes) { + *data = reinterpret_cast(message->mutable_payload()) + 1; + *num_data_bytes = message->payload_size() - sizeof(Header); +} + +void NodeChannel::Start() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (relay) + relay->AddObserver(this); +#endif + + base::AutoLock lock(channel_lock_); + // ShutDown() may have already been called, in which case |channel_| is null. + if (channel_) + channel_->Start(); +} + +void NodeChannel::ShutDown() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (relay) + relay->RemoveObserver(this); +#endif + + base::AutoLock lock(channel_lock_); + if (channel_) { + channel_->ShutDown(); + channel_ = nullptr; + } +} + +void NodeChannel::LeakHandleOnShutdown() { + base::AutoLock lock(channel_lock_); + if (channel_) { + channel_->LeakHandle(); + } +} + +void NodeChannel::NotifyBadMessage(const std::string& error) { + if (!process_error_callback_.is_null()) + process_error_callback_.Run("Received bad user message: " + error); +} + +void NodeChannel::SetRemoteProcessHandle(base::ProcessHandle process_handle) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + base::AutoLock lock(remote_process_handle_lock_); + DCHECK_EQ(base::kNullProcessHandle, remote_process_handle_); + CHECK_NE(remote_process_handle_, base::GetCurrentProcessHandle()); + remote_process_handle_ = process_handle; +#if defined(OS_WIN) + DCHECK(!scoped_remote_process_handle_.is_valid()); + scoped_remote_process_handle_.reset(PlatformHandle(process_handle)); +#endif +} + +bool NodeChannel::HasRemoteProcessHandle() { + base::AutoLock lock(remote_process_handle_lock_); + return remote_process_handle_ != base::kNullProcessHandle; +} + +base::ProcessHandle NodeChannel::CopyRemoteProcessHandle() { + base::AutoLock lock(remote_process_handle_lock_); +#if defined(OS_WIN) + if (remote_process_handle_ != base::kNullProcessHandle) { + // Privileged nodes use this to pass their childrens' process handles to the + // broker on launch. + HANDLE handle = remote_process_handle_; + BOOL result = DuplicateHandle( + base::GetCurrentProcessHandle(), remote_process_handle_, + base::GetCurrentProcessHandle(), &handle, 0, FALSE, + DUPLICATE_SAME_ACCESS); + DPCHECK(result); + return handle; + } + return base::kNullProcessHandle; +#else + return remote_process_handle_; +#endif +} + +void NodeChannel::SetRemoteNodeName(const ports::NodeName& name) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + remote_node_name_ = name; +} + +void NodeChannel::AcceptChild(const ports::NodeName& parent_name, + const ports::NodeName& token) { + AcceptChildData* data; + Channel::MessagePtr message = CreateMessage( + MessageType::ACCEPT_CHILD, sizeof(AcceptChildData), 0, &data); + data->parent_name = parent_name; + data->token = token; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::AcceptParent(const ports::NodeName& token, + const ports::NodeName& child_name) { + AcceptParentData* data; + Channel::MessagePtr message = CreateMessage( + MessageType::ACCEPT_PARENT, sizeof(AcceptParentData), 0, &data); + data->token = token; + data->child_name = child_name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::AcceptPeer(const ports::NodeName& sender_name, + const ports::NodeName& token, + const ports::PortName& port_name) { + AcceptPeerData* data; + Channel::MessagePtr message = + CreateMessage(MessageType::ACCEPT_PEER, sizeof(AcceptPeerData), 0, &data); + data->token = token; + data->peer_name = sender_name; + data->port_name = port_name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::AddBrokerClient(const ports::NodeName& client_name, + base::ProcessHandle process_handle) { + AddBrokerClientData* data; + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector()); +#if defined(OS_WIN) + handles->push_back(PlatformHandle(process_handle)); +#endif + Channel::MessagePtr message = CreateMessage( + MessageType::ADD_BROKER_CLIENT, sizeof(AddBrokerClientData), + handles->size(), &data); + message->SetHandles(std::move(handles)); + data->client_name = client_name; +#if !defined(OS_WIN) + data->process_handle = process_handle; + data->padding = 0; +#endif + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::BrokerClientAdded(const ports::NodeName& client_name, + ScopedPlatformHandle broker_channel) { + BrokerClientAddedData* data; + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector()); + if (broker_channel.is_valid()) + handles->push_back(broker_channel.release()); + Channel::MessagePtr message = CreateMessage( + MessageType::BROKER_CLIENT_ADDED, sizeof(BrokerClientAddedData), + handles->size(), &data); + message->SetHandles(std::move(handles)); + data->client_name = client_name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::AcceptBrokerClient(const ports::NodeName& broker_name, + ScopedPlatformHandle broker_channel) { + AcceptBrokerClientData* data; + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector()); + if (broker_channel.is_valid()) + handles->push_back(broker_channel.release()); + Channel::MessagePtr message = CreateMessage( + MessageType::ACCEPT_BROKER_CLIENT, sizeof(AcceptBrokerClientData), + handles->size(), &data); + message->SetHandles(std::move(handles)); + data->broker_name = broker_name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::PortsMessage(Channel::MessagePtr message) { + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::RequestPortMerge(const ports::PortName& connector_port_name, + const std::string& token) { + RequestPortMergeData* data; + Channel::MessagePtr message = CreateMessage( + MessageType::REQUEST_PORT_MERGE, + sizeof(RequestPortMergeData) + token.size(), 0, &data); + data->connector_port_name = connector_port_name; + memcpy(data + 1, token.data(), token.size()); + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::RequestIntroduction(const ports::NodeName& name) { + IntroductionData* data; + Channel::MessagePtr message = CreateMessage( + MessageType::REQUEST_INTRODUCTION, sizeof(IntroductionData), 0, &data); + data->name = name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::Introduce(const ports::NodeName& name, + ScopedPlatformHandle channel_handle) { + IntroductionData* data; + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector()); + if (channel_handle.is_valid()) + handles->push_back(channel_handle.release()); + Channel::MessagePtr message = CreateMessage( + MessageType::INTRODUCE, sizeof(IntroductionData), handles->size(), &data); + message->SetHandles(std::move(handles)); + data->name = name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::Broadcast(Channel::MessagePtr message) { + DCHECK(!message->has_handles()); + void* data; + Channel::MessagePtr broadcast_message = CreateMessage( + MessageType::BROADCAST, message->data_num_bytes(), 0, &data); + memcpy(data, message->data(), message->data_num_bytes()); + WriteChannelMessage(std::move(broadcast_message)); +} + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) +void NodeChannel::RelayPortsMessage(const ports::NodeName& destination, + Channel::MessagePtr message) { +#if defined(OS_WIN) + DCHECK(message->has_handles()); + + // Note that this is only used on Windows, and on Windows all platform + // handles are included in the message data. We blindly copy all the data + // here and the relay node (the parent) will duplicate handles as needed. + size_t num_bytes = sizeof(RelayPortsMessageData) + message->data_num_bytes(); + RelayPortsMessageData* data; + Channel::MessagePtr relay_message = CreateMessage( + MessageType::RELAY_PORTS_MESSAGE, num_bytes, 0, &data); + data->destination = destination; + memcpy(data + 1, message->data(), message->data_num_bytes()); + + // When the handles are duplicated in the parent, the source handles will + // be closed. If the parent never receives this message then these handles + // will leak, but that means something else has probably broken and the + // sending process won't likely be around much longer. + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + handles->clear(); + +#else + DCHECK(message->has_mach_ports()); + + // On OSX, the handles are extracted from the relayed message and attached to + // the wrapper. The broker then takes the handles attached to the wrapper and + // moves them back to the relayed message. This is necessary because the + // message may contain fds which need to be attached to the outer message so + // that they can be transferred to the broker. + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + size_t num_bytes = sizeof(RelayPortsMessageData) + message->data_num_bytes(); + RelayPortsMessageData* data; + Channel::MessagePtr relay_message = CreateMessage( + MessageType::RELAY_PORTS_MESSAGE, num_bytes, handles->size(), &data); + data->destination = destination; + memcpy(data + 1, message->data(), message->data_num_bytes()); + relay_message->SetHandles(std::move(handles)); +#endif // defined(OS_WIN) + + WriteChannelMessage(std::move(relay_message)); +} + +void NodeChannel::PortsMessageFromRelay(const ports::NodeName& source, + Channel::MessagePtr message) { + size_t num_bytes = sizeof(PortsMessageFromRelayData) + + message->payload_size(); + PortsMessageFromRelayData* data; + Channel::MessagePtr relayed_message = CreateMessage( + MessageType::PORTS_MESSAGE_FROM_RELAY, num_bytes, message->num_handles(), + &data); + data->source = source; + if (message->payload_size()) + memcpy(data + 1, message->payload(), message->payload_size()); + relayed_message->SetHandles(message->TakeHandles()); + WriteChannelMessage(std::move(relayed_message)); +} +#endif // defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + +NodeChannel::NodeChannel(Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner, + const ProcessErrorCallback& process_error_callback) + : delegate_(delegate), + io_task_runner_(io_task_runner), + process_error_callback_(process_error_callback) +#if !defined(OS_NACL_SFI) + , + channel_( + Channel::Create(this, std::move(connection_params), io_task_runner_)) +#endif +{ +} + +NodeChannel::~NodeChannel() { + ShutDown(); +} + +void NodeChannel::OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + RequestContext request_context(RequestContext::Source::SYSTEM); + + // Ensure this NodeChannel stays alive through the extent of this method. The + // delegate may have the only other reference to this object and it may choose + // to drop it here in response to, e.g., a malformed message. + scoped_refptr keepalive = this; + +#if defined(OS_WIN) + // If we receive handles from a known process, rewrite them to our own + // process. This can occur when a privileged node receives handles directly + // from a privileged descendant. + { + base::AutoLock lock(remote_process_handle_lock_); + if (handles && remote_process_handle_ != base::kNullProcessHandle) { + // Note that we explicitly mark the handles as being owned by the sending + // process before rewriting them, in order to accommodate RewriteHandles' + // internal sanity checks. + for (auto& handle : *handles) + handle.owning_process = remote_process_handle_; + if (!Channel::Message::RewriteHandles(remote_process_handle_, + base::GetCurrentProcessHandle(), + handles.get())) { + DLOG(ERROR) << "Received one or more invalid handles."; + } + } else if (handles) { + // Handles received by an unknown process must already be owned by us. + for (auto& handle : *handles) + handle.owning_process = base::GetCurrentProcessHandle(); + } + } +#elif defined(OS_MACOSX) && !defined(OS_IOS) + // If we're not the root, receive any mach ports from the message. If we're + // the root, the only message containing mach ports should be a + // RELAY_PORTS_MESSAGE. + { + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (handles && !relay) { + if (!MachPortRelay::ReceivePorts(handles.get())) { + LOG(ERROR) << "Error receiving mach ports."; + } + } + } +#endif // defined(OS_WIN) + + + if (payload_size <= sizeof(Header)) { + delegate_->OnChannelError(remote_node_name_, this); + return; + } + + const Header* header = static_cast(payload); + switch (header->type) { + case MessageType::ACCEPT_CHILD: { + const AcceptChildData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + delegate_->OnAcceptChild(remote_node_name_, data->parent_name, + data->token); + return; + } + break; + } + + case MessageType::ACCEPT_PARENT: { + const AcceptParentData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + delegate_->OnAcceptParent(remote_node_name_, data->token, + data->child_name); + return; + } + break; + } + + case MessageType::ADD_BROKER_CLIENT: { + const AddBrokerClientData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + ScopedPlatformHandle process_handle; +#if defined(OS_WIN) + if (!handles || handles->size() != 1) { + DLOG(ERROR) << "Dropping invalid AddBrokerClient message."; + break; + } + process_handle = ScopedPlatformHandle(handles->at(0)); + handles->clear(); + delegate_->OnAddBrokerClient(remote_node_name_, data->client_name, + process_handle.release().handle); +#else + if (handles && handles->size() != 0) { + DLOG(ERROR) << "Dropping invalid AddBrokerClient message."; + break; + } + delegate_->OnAddBrokerClient(remote_node_name_, data->client_name, + data->process_handle); +#endif + return; + } + break; + } + + case MessageType::BROKER_CLIENT_ADDED: { + const BrokerClientAddedData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + ScopedPlatformHandle broker_channel; + if (!handles || handles->size() != 1) { + DLOG(ERROR) << "Dropping invalid BrokerClientAdded message."; + break; + } + broker_channel = ScopedPlatformHandle(handles->at(0)); + handles->clear(); + delegate_->OnBrokerClientAdded(remote_node_name_, data->client_name, + std::move(broker_channel)); + return; + } + break; + } + + case MessageType::ACCEPT_BROKER_CLIENT: { + const AcceptBrokerClientData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + ScopedPlatformHandle broker_channel; + if (handles && handles->size() > 1) { + DLOG(ERROR) << "Dropping invalid AcceptBrokerClient message."; + break; + } + if (handles && handles->size() == 1) { + broker_channel = ScopedPlatformHandle(handles->at(0)); + handles->clear(); + } + delegate_->OnAcceptBrokerClient(remote_node_name_, data->broker_name, + std::move(broker_channel)); + return; + } + break; + } + + case MessageType::PORTS_MESSAGE: { + size_t num_handles = handles ? handles->size() : 0; + Channel::MessagePtr message( + new Channel::Message(payload_size, num_handles)); + message->SetHandles(std::move(handles)); + memcpy(message->mutable_payload(), payload, payload_size); + delegate_->OnPortsMessage(remote_node_name_, std::move(message)); + return; + } + + case MessageType::REQUEST_PORT_MERGE: { + const RequestPortMergeData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + // Don't accept an empty token. + size_t token_size = payload_size - sizeof(*data) - sizeof(Header); + if (token_size == 0) + break; + std::string token(reinterpret_cast(data + 1), token_size); + delegate_->OnRequestPortMerge(remote_node_name_, + data->connector_port_name, token); + return; + } + break; + } + + case MessageType::REQUEST_INTRODUCTION: { + const IntroductionData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + delegate_->OnRequestIntroduction(remote_node_name_, data->name); + return; + } + break; + } + + case MessageType::INTRODUCE: { + const IntroductionData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + if (handles && handles->size() > 1) { + DLOG(ERROR) << "Dropping invalid introduction message."; + break; + } + ScopedPlatformHandle channel_handle; + if (handles && handles->size() == 1) { + channel_handle = ScopedPlatformHandle(handles->at(0)); + handles->clear(); + } + delegate_->OnIntroduce(remote_node_name_, data->name, + std::move(channel_handle)); + return; + } + break; + } + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + case MessageType::RELAY_PORTS_MESSAGE: { + base::ProcessHandle from_process; + { + base::AutoLock lock(remote_process_handle_lock_); + from_process = remote_process_handle_; + } + const RelayPortsMessageData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + // Don't try to relay an empty message. + if (payload_size <= sizeof(Header) + sizeof(RelayPortsMessageData)) + break; + + const void* message_start = data + 1; + Channel::MessagePtr message = Channel::Message::Deserialize( + message_start, payload_size - sizeof(Header) - sizeof(*data)); + if (!message) { + DLOG(ERROR) << "Dropping invalid relay message."; + break; + } + #if defined(OS_MACOSX) && !defined(OS_IOS) + message->SetHandles(std::move(handles)); + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (!relay) { + LOG(ERROR) << "Receiving mach ports without a port relay from " + << remote_node_name_ << ". Dropping message."; + break; + } + { + base::AutoLock lock(pending_mach_messages_lock_); + if (relay->port_provider()->TaskForPid(from_process) == + MACH_PORT_NULL) { + pending_relay_messages_.push( + std::make_pair(data->destination, std::move(message))); + break; + } + } + #endif + delegate_->OnRelayPortsMessage(remote_node_name_, from_process, + data->destination, std::move(message)); + return; + } + break; + } +#endif + + case MessageType::BROADCAST: { + if (payload_size <= sizeof(Header)) + break; + const void* data = static_cast( + reinterpret_cast(payload) + 1); + Channel::MessagePtr message = + Channel::Message::Deserialize(data, payload_size - sizeof(Header)); + if (!message || message->has_handles()) { + DLOG(ERROR) << "Dropping invalid broadcast message."; + break; + } + delegate_->OnBroadcast(remote_node_name_, std::move(message)); + return; + } + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + case MessageType::PORTS_MESSAGE_FROM_RELAY: + const PortsMessageFromRelayData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + size_t num_bytes = payload_size - sizeof(*data); + if (num_bytes < sizeof(Header)) + break; + num_bytes -= sizeof(Header); + + size_t num_handles = handles ? handles->size() : 0; + Channel::MessagePtr message( + new Channel::Message(num_bytes, num_handles)); + message->SetHandles(std::move(handles)); + if (num_bytes) + memcpy(message->mutable_payload(), data + 1, num_bytes); + delegate_->OnPortsMessageFromRelay( + remote_node_name_, data->source, std::move(message)); + return; + } + break; + +#endif // defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + + case MessageType::ACCEPT_PEER: { + const AcceptPeerData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + delegate_->OnAcceptPeer(remote_node_name_, data->token, data->peer_name, + data->port_name); + return; + } + break; + } + + default: + break; + } + + DLOG(ERROR) << "Received invalid message. Closing channel."; + delegate_->OnChannelError(remote_node_name_, this); +} + +void NodeChannel::OnChannelError() { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + RequestContext request_context(RequestContext::Source::SYSTEM); + + ShutDown(); + // |OnChannelError()| may cause |this| to be destroyed, but still need access + // to the name name after that destruction. So may a copy of + // |remote_node_name_| so it can be used if |this| becomes destroyed. + ports::NodeName node_name = remote_node_name_; + delegate_->OnChannelError(node_name, this); +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +void NodeChannel::OnProcessReady(base::ProcessHandle process) { + io_task_runner_->PostTask(FROM_HERE, base::Bind( + &NodeChannel::ProcessPendingMessagesWithMachPorts, this)); +} + +void NodeChannel::ProcessPendingMessagesWithMachPorts() { + MachPortRelay* relay = delegate_->GetMachPortRelay(); + DCHECK(relay); + + base::ProcessHandle remote_process_handle; + { + base::AutoLock lock(remote_process_handle_lock_); + remote_process_handle = remote_process_handle_; + } + PendingMessageQueue pending_writes; + PendingRelayMessageQueue pending_relays; + { + base::AutoLock lock(pending_mach_messages_lock_); + pending_writes.swap(pending_write_messages_); + pending_relays.swap(pending_relay_messages_); + } + + while (!pending_writes.empty()) { + Channel::MessagePtr message = std::move(pending_writes.front()); + pending_writes.pop(); + if (!relay->SendPortsToProcess(message.get(), remote_process_handle)) { + LOG(ERROR) << "Error on sending mach ports. Remote process is likely " + << "gone. Dropping message."; + return; + } + + base::AutoLock lock(channel_lock_); + if (!channel_) { + DLOG(ERROR) << "Dropping message on closed channel."; + break; + } else { + channel_->Write(std::move(message)); + } + } + + // Ensure this NodeChannel stays alive while flushing relay messages. + scoped_refptr keepalive = this; + + while (!pending_relays.empty()) { + ports::NodeName destination = pending_relays.front().first; + Channel::MessagePtr message = std::move(pending_relays.front().second); + pending_relays.pop(); + delegate_->OnRelayPortsMessage(remote_node_name_, remote_process_handle, + destination, std::move(message)); + } +} +#endif + +void NodeChannel::WriteChannelMessage(Channel::MessagePtr message) { +#if defined(OS_WIN) + // Map handles to the destination process. Note: only messages from a + // privileged node should contain handles on Windows. If an unprivileged + // node needs to send handles, it should do so via RelayPortsMessage which + // stashes the handles in the message in such a way that they go undetected + // here (they'll be unpacked and duplicated by a privileged parent.) + + if (message->has_handles()) { + base::ProcessHandle remote_process_handle; + { + base::AutoLock lock(remote_process_handle_lock_); + remote_process_handle = remote_process_handle_; + } + + // Rewrite outgoing handles if we have a handle to the destination process. + if (remote_process_handle != base::kNullProcessHandle) { + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + if (!Channel::Message::RewriteHandles(base::GetCurrentProcessHandle(), + remote_process_handle, + handles.get())) { + DLOG(ERROR) << "Failed to duplicate one or more outgoing handles."; + } + message->SetHandles(std::move(handles)); + } + } +#elif defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, we need to transfer mach ports to the destination process before + // transferring the message itself. + if (message->has_mach_ports()) { + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (relay) { + base::ProcessHandle remote_process_handle; + { + base::AutoLock lock(remote_process_handle_lock_); + // Expect that the receiving node is a child. + DCHECK(remote_process_handle_ != base::kNullProcessHandle); + remote_process_handle = remote_process_handle_; + } + { + base::AutoLock lock(pending_mach_messages_lock_); + if (relay->port_provider()->TaskForPid(remote_process_handle) == + MACH_PORT_NULL) { + // It is also possible for TaskForPid() to return MACH_PORT_NULL when + // the process has started, then died. In that case, the queued + // message will never be processed. But that's fine since we're about + // to die anyway. + pending_write_messages_.push(std::move(message)); + return; + } + } + + if (!relay->SendPortsToProcess(message.get(), remote_process_handle)) { + LOG(ERROR) << "Error on sending mach ports. Remote process is likely " + << "gone. Dropping message."; + return; + } + } + } +#endif + + base::AutoLock lock(channel_lock_); + if (!channel_) + DLOG(ERROR) << "Dropping message on closed channel."; + else + channel_->Write(std::move(message)); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/node_channel.h b/mojo/edk/system/node_channel.h new file mode 100644 index 0000000000000000000000000000000000000000..95dc3410eb5bf94cb97a760c6e87465ffb8709a8 --- /dev/null +++ b/mojo/edk/system/node_channel.h @@ -0,0 +1,219 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_NODE_CHANNEL_H_ +#define MOJO_EDK_SYSTEM_NODE_CHANNEL_H_ + +#include +#include +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/process/process_handle.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/connection_params.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/channel.h" +#include "mojo/edk/system/ports/name.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "mojo/edk/system/mach_port_relay.h" +#endif + +namespace mojo { +namespace edk { + +// Wraps a Channel to send and receive Node control messages. +class NodeChannel : public base::RefCountedThreadSafe, + public Channel::Delegate +#if defined(OS_MACOSX) && !defined(OS_IOS) + , public MachPortRelay::Observer +#endif + { + public: + class Delegate { + public: + virtual ~Delegate() {} + virtual void OnAcceptChild(const ports::NodeName& from_node, + const ports::NodeName& parent_name, + const ports::NodeName& token) = 0; + virtual void OnAcceptParent(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& child_name) = 0; + virtual void OnAddBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& client_name, + base::ProcessHandle process_handle) = 0; + virtual void OnBrokerClientAdded(const ports::NodeName& from_node, + const ports::NodeName& client_name, + ScopedPlatformHandle broker_channel) = 0; + virtual void OnAcceptBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& broker_name, + ScopedPlatformHandle broker_channel) = 0; + virtual void OnPortsMessage(const ports::NodeName& from_node, + Channel::MessagePtr message) = 0; + virtual void OnRequestPortMerge(const ports::NodeName& from_node, + const ports::PortName& connector_port_name, + const std::string& token) = 0; + virtual void OnRequestIntroduction(const ports::NodeName& from_node, + const ports::NodeName& name) = 0; + virtual void OnIntroduce(const ports::NodeName& from_node, + const ports::NodeName& name, + ScopedPlatformHandle channel_handle) = 0; + virtual void OnBroadcast(const ports::NodeName& from_node, + Channel::MessagePtr message) = 0; +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + virtual void OnRelayPortsMessage(const ports::NodeName& from_node, + base::ProcessHandle from_process, + const ports::NodeName& destination, + Channel::MessagePtr message) = 0; + virtual void OnPortsMessageFromRelay(const ports::NodeName& from_node, + const ports::NodeName& source_node, + Channel::MessagePtr message) = 0; +#endif + virtual void OnAcceptPeer(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& peer_name, + const ports::PortName& port_name) = 0; + virtual void OnChannelError(const ports::NodeName& node, + NodeChannel* channel) = 0; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + virtual MachPortRelay* GetMachPortRelay() = 0; +#endif + }; + + static scoped_refptr Create( + Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner, + const ProcessErrorCallback& process_error_callback); + + static Channel::MessagePtr CreatePortsMessage(size_t payload_size, + void** payload, + size_t num_handles); + + static void GetPortsMessageData(Channel::Message* message, void** data, + size_t* num_data_bytes); + + // Start receiving messages. + void Start(); + + // Permanently stop the channel from sending or receiving messages. + void ShutDown(); + + // Leaks the pipe handle instead of closing it on shutdown. + void LeakHandleOnShutdown(); + + // Invokes the bad message callback for this channel, if any. + void NotifyBadMessage(const std::string& error); + + // Note: On Windows, we take ownership of the remote process handle. + void SetRemoteProcessHandle(base::ProcessHandle process_handle); + bool HasRemoteProcessHandle(); + // Note: The returned |ProcessHandle| is owned by the caller and should be + // freed if necessary. + base::ProcessHandle CopyRemoteProcessHandle(); + + // Used for context in Delegate calls (via |from_node| arguments.) + void SetRemoteNodeName(const ports::NodeName& name); + + void AcceptChild(const ports::NodeName& parent_name, + const ports::NodeName& token); + void AcceptParent(const ports::NodeName& token, + const ports::NodeName& child_name); + void AcceptPeer(const ports::NodeName& sender_name, + const ports::NodeName& token, + const ports::PortName& port_name); + void AddBrokerClient(const ports::NodeName& client_name, + base::ProcessHandle process_handle); + void BrokerClientAdded(const ports::NodeName& client_name, + ScopedPlatformHandle broker_channel); + void AcceptBrokerClient(const ports::NodeName& broker_name, + ScopedPlatformHandle broker_channel); + void PortsMessage(Channel::MessagePtr message); + void RequestPortMerge(const ports::PortName& connector_port_name, + const std::string& token); + void RequestIntroduction(const ports::NodeName& name); + void Introduce(const ports::NodeName& name, + ScopedPlatformHandle channel_handle); + void Broadcast(Channel::MessagePtr message); + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + // Relay the message to the specified node via this channel. This is used to + // pass windows handles between two processes that do not have permission to + // duplicate handles into the other's address space. The relay process is + // assumed to have that permission. + void RelayPortsMessage(const ports::NodeName& destination, + Channel::MessagePtr message); + + // Sends a message to its destination from a relay. This is interpreted by the + // receiver similarly to PortsMessage, but the original source node is + // provided as additional message metadata from the (trusted) relay node. + void PortsMessageFromRelay(const ports::NodeName& source, + Channel::MessagePtr message); +#endif + + private: + friend class base::RefCountedThreadSafe; + + using PendingMessageQueue = std::queue; + using PendingRelayMessageQueue = + std::queue>; + + NodeChannel(Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner, + const ProcessErrorCallback& process_error_callback); + ~NodeChannel() override; + + // Channel::Delegate: + void OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) override; + void OnChannelError() override; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + // MachPortRelay::Observer: + void OnProcessReady(base::ProcessHandle process) override; + + void ProcessPendingMessagesWithMachPorts(); +#endif + + void WriteChannelMessage(Channel::MessagePtr message); + + Delegate* const delegate_; + const scoped_refptr io_task_runner_; + const ProcessErrorCallback process_error_callback_; + + base::Lock channel_lock_; + scoped_refptr channel_; + + // Must only be accessed from |io_task_runner_|'s thread. + ports::NodeName remote_node_name_; + + base::Lock remote_process_handle_lock_; + base::ProcessHandle remote_process_handle_ = base::kNullProcessHandle; +#if defined(OS_WIN) + ScopedPlatformHandle scoped_remote_process_handle_; +#endif + +#if defined(OS_MACOSX) && !defined(OS_IOS) + base::Lock pending_mach_messages_lock_; + PendingMessageQueue pending_write_messages_; + PendingRelayMessageQueue pending_relay_messages_; +#endif + + DISALLOW_COPY_AND_ASSIGN(NodeChannel); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_NODE_CHANNEL_H_ diff --git a/mojo/edk/system/node_controller.cc b/mojo/edk/system/node_controller.cc new file mode 100644 index 0000000000000000000000000000000000000000..e608f0c009eedba6a0da4bd106e6b4d8a95b853c --- /dev/null +++ b/mojo/edk/system/node_controller.cc @@ -0,0 +1,1475 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/node_controller.h" + +#include +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram_macros.h" +#include "base/process/process_handle.h" +#include "base/rand_util.h" +#include "base/time/time.h" +#include "base/timer/elapsed_timer.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/named_platform_channel_pair.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/system/broker.h" +#include "mojo/edk/system/broker_host.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/ports_message.h" +#include "mojo/edk/system/request_context.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "mojo/edk/system/mach_port_relay.h" +#endif + +#if !defined(OS_NACL) +#include "crypto/random.h" +#endif + +namespace mojo { +namespace edk { + +namespace { + +#if defined(OS_NACL) +template +void GenerateRandomName(T* out) { base::RandBytes(out, sizeof(T)); } +#else +template +void GenerateRandomName(T* out) { crypto::RandBytes(out, sizeof(T)); } +#endif + +ports::NodeName GetRandomNodeName() { + ports::NodeName name; + GenerateRandomName(&name); + return name; +} + +void RecordPeerCount(size_t count) { + DCHECK_LE(count, static_cast(std::numeric_limits::max())); + + // 8k is the maximum number of file descriptors allowed in Chrome. + UMA_HISTOGRAM_CUSTOM_COUNTS("Mojo.System.Node.ConnectedPeers", + static_cast(count), + 1 /* min */, + 8000 /* max */, + 50 /* bucket count */); +} + +void RecordPendingChildCount(size_t count) { + DCHECK_LE(count, static_cast(std::numeric_limits::max())); + + // 8k is the maximum number of file descriptors allowed in Chrome. + UMA_HISTOGRAM_CUSTOM_COUNTS("Mojo.System.Node.PendingChildren", + static_cast(count), + 1 /* min */, + 8000 /* max */, + 50 /* bucket count */); +} + +bool ParsePortsMessage(Channel::Message* message, + void** data, + size_t* num_data_bytes, + size_t* num_header_bytes, + size_t* num_payload_bytes, + size_t* num_ports_bytes) { + DCHECK(data && num_data_bytes && num_header_bytes && num_payload_bytes && + num_ports_bytes); + + NodeChannel::GetPortsMessageData(message, data, num_data_bytes); + if (!*num_data_bytes) + return false; + + if (!ports::Message::Parse(*data, *num_data_bytes, num_header_bytes, + num_payload_bytes, num_ports_bytes)) { + return false; + } + + return true; +} + +// Used by NodeController to watch for shutdown. Since no IO can happen once +// the IO thread is killed, the NodeController can cleanly drop all its peers +// at that time. +class ThreadDestructionObserver : + public base::MessageLoop::DestructionObserver { + public: + static void Create(scoped_refptr task_runner, + const base::Closure& callback) { + if (task_runner->RunsTasksOnCurrentThread()) { + // Owns itself. + new ThreadDestructionObserver(callback); + } else { + task_runner->PostTask(FROM_HERE, + base::Bind(&Create, task_runner, callback)); + } + } + + private: + explicit ThreadDestructionObserver(const base::Closure& callback) + : callback_(callback) { + base::MessageLoop::current()->AddDestructionObserver(this); + } + + ~ThreadDestructionObserver() override { + base::MessageLoop::current()->RemoveDestructionObserver(this); + } + + // base::MessageLoop::DestructionObserver: + void WillDestroyCurrentMessageLoop() override { + callback_.Run(); + delete this; + } + + const base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(ThreadDestructionObserver); +}; + +} // namespace + +NodeController::~NodeController() {} + +NodeController::NodeController(Core* core) + : core_(core), + name_(GetRandomNodeName()), + node_(new ports::Node(name_, this)) { + DVLOG(1) << "Initializing node " << name_; +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +void NodeController::CreateMachPortRelay( + base::PortProvider* port_provider) { + base::AutoLock lock(mach_port_relay_lock_); + DCHECK(!mach_port_relay_); + mach_port_relay_.reset(new MachPortRelay(port_provider)); +} +#endif + +void NodeController::SetIOTaskRunner( + scoped_refptr task_runner) { + io_task_runner_ = task_runner; + ThreadDestructionObserver::Create( + io_task_runner_, + base::Bind(&NodeController::DropAllPeers, base::Unretained(this))); +} + +void NodeController::ConnectToChild( + base::ProcessHandle process_handle, + ConnectionParams connection_params, + const std::string& child_token, + const ProcessErrorCallback& process_error_callback) { + // Generate the temporary remote node name here so that it can be associated + // with the embedder's child_token. If an error occurs in the child process + // after it is launched, but before any reserved ports are connected, this can + // be used to clean up any dangling ports. + ports::NodeName node_name; + GenerateRandomName(&node_name); + + { + base::AutoLock lock(reserved_ports_lock_); + bool inserted = pending_child_tokens_.insert( + std::make_pair(node_name, child_token)).second; + DCHECK(inserted); + } + +#if defined(OS_WIN) + // On Windows, we need to duplicate the process handle because we have no + // control over its lifetime and it may become invalid by the time the posted + // task runs. + HANDLE dup_handle = INVALID_HANDLE_VALUE; + BOOL ok = ::DuplicateHandle( + base::GetCurrentProcessHandle(), process_handle, + base::GetCurrentProcessHandle(), &dup_handle, + 0, FALSE, DUPLICATE_SAME_ACCESS); + DPCHECK(ok); + process_handle = dup_handle; +#endif + + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&NodeController::ConnectToChildOnIOThread, + base::Unretained(this), process_handle, + base::Passed(&connection_params), node_name, + process_error_callback)); +} + +void NodeController::CloseChildPorts(const std::string& child_token) { + std::vector ports_to_close; + { + std::vector port_tokens; + base::AutoLock lock(reserved_ports_lock_); + for (const auto& port : reserved_ports_) { + if (port.second.child_token == child_token) { + DVLOG(1) << "Closing reserved port " << port.second.port.name(); + ports_to_close.push_back(port.second.port); + port_tokens.push_back(port.first); + } + } + + for (const auto& token : port_tokens) + reserved_ports_.erase(token); + } + + for (const auto& port : ports_to_close) + node_->ClosePort(port); + + // Ensure local port closure messages are processed. + AcceptIncomingMessages(); +} + +void NodeController::ClosePeerConnection(const std::string& peer_token) { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&NodeController::ClosePeerConnectionOnIOThread, + base::Unretained(this), peer_token)); +} + +void NodeController::ConnectToParent(ConnectionParams connection_params) { +#if !defined(OS_MACOSX) && !defined(OS_NACL_SFI) + // Use the bootstrap channel for the broker and receive the node's channel + // synchronously as the first message from the broker. + base::ElapsedTimer timer; + broker_.reset(new Broker(connection_params.TakeChannelHandle())); + ScopedPlatformHandle platform_handle = broker_->GetParentPlatformHandle(); + UMA_HISTOGRAM_TIMES("Mojo.System.GetParentPlatformHandleSyncTime", + timer.Elapsed()); + + if (!platform_handle.is_valid()) { + // Most likely the browser side of the channel has already been closed and + // the broker was unable to negotiate a NodeChannel pipe. In this case we + // can cancel parent connection. + DVLOG(1) << "Cannot connect to invalid parent channel."; + CancelPendingPortMerges(); + return; + } + connection_params = ConnectionParams(std::move(platform_handle)); +#endif + + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NodeController::ConnectToParentOnIOThread, + base::Unretained(this), base::Passed(&connection_params))); +} + +void NodeController::ConnectToPeer(ConnectionParams connection_params, + const ports::PortRef& port, + const std::string& peer_token) { + ports::NodeName node_name; + GenerateRandomName(&node_name); + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NodeController::ConnectToPeerOnIOThread, + base::Unretained(this), base::Passed(&connection_params), + node_name, port, peer_token)); +} + +void NodeController::SetPortObserver(const ports::PortRef& port, + scoped_refptr observer) { + node_->SetUserData(port, std::move(observer)); +} + +void NodeController::ClosePort(const ports::PortRef& port) { + SetPortObserver(port, nullptr); + int rv = node_->ClosePort(port); + DCHECK_EQ(rv, ports::OK) << " Failed to close port: " << port.name(); + + AcceptIncomingMessages(); +} + +int NodeController::SendMessage(const ports::PortRef& port, + std::unique_ptr message) { + ports::ScopedMessage ports_message(message.release()); + int rv = node_->SendMessage(port, std::move(ports_message)); + + AcceptIncomingMessages(); + return rv; +} + +void NodeController::ReservePort(const std::string& token, + const ports::PortRef& port, + const std::string& child_token) { + DVLOG(2) << "Reserving port " << port.name() << "@" << name_ << " for token " + << token; + + base::AutoLock lock(reserved_ports_lock_); + auto result = reserved_ports_.insert( + std::make_pair(token, ReservedPort{port, child_token})); + DCHECK(result.second); +} + +void NodeController::MergePortIntoParent(const std::string& token, + const ports::PortRef& port) { + bool was_merged = false; + { + // This request may be coming from within the process that reserved the + // "parent" side (e.g. for Chrome single-process mode), so if this token is + // reserved locally, merge locally instead. + base::AutoLock lock(reserved_ports_lock_); + auto it = reserved_ports_.find(token); + if (it != reserved_ports_.end()) { + node_->MergePorts(port, name_, it->second.port.name()); + reserved_ports_.erase(it); + was_merged = true; + } + } + if (was_merged) { + AcceptIncomingMessages(); + return; + } + + scoped_refptr parent; + bool reject_merge = false; + { + // Hold |pending_port_merges_lock_| while getting |parent|. Otherwise, + // there is a race where the parent can be set, and |pending_port_merges_| + // be processed between retrieving |parent| and adding the merge to + // |pending_port_merges_|. + base::AutoLock lock(pending_port_merges_lock_); + parent = GetParentChannel(); + if (reject_pending_merges_) { + reject_merge = true; + } else if (!parent) { + pending_port_merges_.push_back(std::make_pair(token, port)); + return; + } + } + if (reject_merge) { + node_->ClosePort(port); + DVLOG(2) << "Rejecting port merge for token " << token + << " due to closed parent channel."; + AcceptIncomingMessages(); + return; + } + + parent->RequestPortMerge(port.name(), token); +} + +int NodeController::MergeLocalPorts(const ports::PortRef& port0, + const ports::PortRef& port1) { + int rv = node_->MergeLocalPorts(port0, port1); + AcceptIncomingMessages(); + return rv; +} + +scoped_refptr NodeController::CreateSharedBuffer( + size_t num_bytes) { +#if !defined(OS_MACOSX) && !defined(OS_NACL_SFI) + // Shared buffer creation failure is fatal, so always use the broker when we + // have one. This does mean that a non-root process that has children will use + // the broker for shared buffer creation even though that process is + // privileged. + if (broker_) { + return broker_->GetSharedBuffer(num_bytes); + } +#endif + return PlatformSharedBuffer::Create(num_bytes); +} + +void NodeController::RequestShutdown(const base::Closure& callback) { + { + base::AutoLock lock(shutdown_lock_); + shutdown_callback_ = callback; + shutdown_callback_flag_.Set(true); + } + + AttemptShutdownIfRequested(); +} + +void NodeController::NotifyBadMessageFrom(const ports::NodeName& source_node, + const std::string& error) { + scoped_refptr peer = GetPeerChannel(source_node); + if (peer) + peer->NotifyBadMessage(error); +} + +void NodeController::ConnectToChildOnIOThread( + base::ProcessHandle process_handle, + ConnectionParams connection_params, + ports::NodeName token, + const ProcessErrorCallback& process_error_callback) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + +#if !defined(OS_MACOSX) && !defined(OS_NACL) + PlatformChannelPair node_channel; + ScopedPlatformHandle server_handle = node_channel.PassServerHandle(); + // BrokerHost owns itself. + BrokerHost* broker_host = + new BrokerHost(process_handle, connection_params.TakeChannelHandle()); + bool channel_ok = broker_host->SendChannel(node_channel.PassClientHandle()); + +#if defined(OS_WIN) + if (!channel_ok) { + // On Windows the above operation may fail if the channel is crossing a + // session boundary. In that case we fall back to a named pipe. + NamedPlatformChannelPair named_channel; + server_handle = named_channel.PassServerHandle(); + broker_host->SendNamedChannel(named_channel.handle().name); + } +#else + CHECK(channel_ok); +#endif // defined(OS_WIN) + + scoped_refptr channel = + NodeChannel::Create(this, ConnectionParams(std::move(server_handle)), + io_task_runner_, process_error_callback); + +#else // !defined(OS_MACOSX) && !defined(OS_NACL) + scoped_refptr channel = + NodeChannel::Create(this, std::move(connection_params), io_task_runner_, + process_error_callback); +#endif // !defined(OS_MACOSX) && !defined(OS_NACL) + + // We set up the child channel with a temporary name so it can be identified + // as a pending child if it writes any messages to the channel. We may start + // receiving messages from it (though we shouldn't) as soon as Start() is + // called below. + + pending_children_.insert(std::make_pair(token, channel)); + RecordPendingChildCount(pending_children_.size()); + + channel->SetRemoteNodeName(token); + channel->SetRemoteProcessHandle(process_handle); + channel->Start(); + + channel->AcceptChild(name_, token); +} + +void NodeController::ConnectToParentOnIOThread( + ConnectionParams connection_params) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + { + base::AutoLock lock(parent_lock_); + DCHECK(parent_name_ == ports::kInvalidNodeName); + + // At this point we don't know the parent's name, so we can't yet insert it + // into our |peers_| map. That will happen as soon as we receive an + // AcceptChild message from them. + bootstrap_parent_channel_ = + NodeChannel::Create(this, std::move(connection_params), io_task_runner_, + ProcessErrorCallback()); + // Prevent the parent pipe handle from being closed on shutdown. Pipe + // closure is used by the parent to detect the child process has exited. + // Relying on message pipes to be closed is not enough because the parent + // may see the message pipe closure before the child is dead, causing the + // child process to be unexpectedly SIGKILL'd. + bootstrap_parent_channel_->LeakHandleOnShutdown(); + } + bootstrap_parent_channel_->Start(); +} + +void NodeController::ConnectToPeerOnIOThread(ConnectionParams connection_params, + ports::NodeName token, + ports::PortRef port, + const std::string& peer_token) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + scoped_refptr channel = NodeChannel::Create( + this, std::move(connection_params), io_task_runner_, {}); + peer_connections_.insert( + {token, PeerConnection{channel, port, peer_token}}); + peers_by_token_.insert({peer_token, token}); + + channel->SetRemoteNodeName(token); + channel->Start(); + + channel->AcceptPeer(name_, token, port.name()); +} + +void NodeController::ClosePeerConnectionOnIOThread( + const std::string& peer_token) { + RequestContext request_context(RequestContext::Source::SYSTEM); + auto peer = peers_by_token_.find(peer_token); + // The connection may already be closed. + if (peer == peers_by_token_.end()) + return; + + // |peer| may be removed so make a copy of |name|. + ports::NodeName name = peer->second; + DropPeer(name, nullptr); +} + +scoped_refptr NodeController::GetPeerChannel( + const ports::NodeName& name) { + base::AutoLock lock(peers_lock_); + auto it = peers_.find(name); + if (it == peers_.end()) + return nullptr; + return it->second; +} + +scoped_refptr NodeController::GetParentChannel() { + ports::NodeName parent_name; + { + base::AutoLock lock(parent_lock_); + parent_name = parent_name_; + } + return GetPeerChannel(parent_name); +} + +scoped_refptr NodeController::GetBrokerChannel() { + ports::NodeName broker_name; + { + base::AutoLock lock(broker_lock_); + broker_name = broker_name_; + } + return GetPeerChannel(broker_name); +} + +void NodeController::AddPeer(const ports::NodeName& name, + scoped_refptr channel, + bool start_channel) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + DCHECK(name != ports::kInvalidNodeName); + DCHECK(channel); + + channel->SetRemoteNodeName(name); + + OutgoingMessageQueue pending_messages; + { + base::AutoLock lock(peers_lock_); + if (peers_.find(name) != peers_.end()) { + // This can happen normally if two nodes race to be introduced to each + // other. The losing pipe will be silently closed and introduction should + // not be affected. + DVLOG(1) << "Ignoring duplicate peer name " << name; + return; + } + + auto result = peers_.insert(std::make_pair(name, channel)); + DCHECK(result.second); + + DVLOG(2) << "Accepting new peer " << name << " on node " << name_; + + RecordPeerCount(peers_.size()); + + auto it = pending_peer_messages_.find(name); + if (it != pending_peer_messages_.end()) { + std::swap(pending_messages, it->second); + pending_peer_messages_.erase(it); + } + } + + if (start_channel) + channel->Start(); + + // Flush any queued message we need to deliver to this node. + while (!pending_messages.empty()) { + channel->PortsMessage(std::move(pending_messages.front())); + pending_messages.pop(); + } +} + +void NodeController::DropPeer(const ports::NodeName& name, + NodeChannel* channel) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + { + base::AutoLock lock(peers_lock_); + auto it = peers_.find(name); + + if (it != peers_.end()) { + ports::NodeName peer = it->first; + peers_.erase(it); + DVLOG(1) << "Dropped peer " << peer; + } + + pending_peer_messages_.erase(name); + pending_children_.erase(name); + + RecordPeerCount(peers_.size()); + RecordPendingChildCount(pending_children_.size()); + } + + std::vector ports_to_close; + { + // Clean up any reserved ports. + base::AutoLock lock(reserved_ports_lock_); + auto it = pending_child_tokens_.find(name); + if (it != pending_child_tokens_.end()) { + const std::string& child_token = it->second; + + std::vector port_tokens; + for (const auto& port : reserved_ports_) { + if (port.second.child_token == child_token) { + DVLOG(1) << "Closing reserved port: " << port.second.port.name(); + ports_to_close.push_back(port.second.port); + port_tokens.push_back(port.first); + } + } + + // We have to erase reserved ports in a two-step manner because the usual + // manner of using the returned iterator from map::erase isn't technically + // valid in C++11 (although it is in C++14). + for (const auto& token : port_tokens) + reserved_ports_.erase(token); + + pending_child_tokens_.erase(it); + } + } + + bool is_parent; + { + base::AutoLock lock(parent_lock_); + is_parent = (name == parent_name_ || channel == bootstrap_parent_channel_); + } + + // If the error comes from the parent channel, we also need to cancel any + // port merge requests, so that errors can be propagated to the message + // pipes. + if (is_parent) + CancelPendingPortMerges(); + + auto peer = peer_connections_.find(name); + if (peer != peer_connections_.end()) { + peers_by_token_.erase(peer->second.peer_token); + ports_to_close.push_back(peer->second.local_port); + peer_connections_.erase(peer); + } + + for (const auto& port : ports_to_close) + node_->ClosePort(port); + + node_->LostConnectionToNode(name); + + AcceptIncomingMessages(); +} + +void NodeController::SendPeerMessage(const ports::NodeName& name, + ports::ScopedMessage message) { + Channel::MessagePtr channel_message = + static_cast(message.get())->TakeChannelMessage(); + + scoped_refptr peer = GetPeerChannel(name); +#if defined(OS_WIN) + if (channel_message->has_handles()) { + // If we're sending a message with handles we aren't the destination + // node's parent or broker (i.e. we don't know its process handle), ask + // the broker to relay for us. + scoped_refptr broker = GetBrokerChannel(); + if (!peer || !peer->HasRemoteProcessHandle()) { + if (broker) { + broker->RelayPortsMessage(name, std::move(channel_message)); + } else { + base::AutoLock lock(broker_lock_); + pending_relay_messages_[name].emplace(std::move(channel_message)); + } + return; + } + } +#elif defined(OS_MACOSX) && !defined(OS_IOS) + if (channel_message->has_mach_ports()) { + // Messages containing Mach ports are always routed through the broker, even + // if the broker process is the intended recipient. + bool use_broker = false; + { + base::AutoLock lock(parent_lock_); + use_broker = (bootstrap_parent_channel_ || + parent_name_ != ports::kInvalidNodeName); + } + if (use_broker) { + scoped_refptr broker = GetBrokerChannel(); + if (broker) { + broker->RelayPortsMessage(name, std::move(channel_message)); + } else { + base::AutoLock lock(broker_lock_); + pending_relay_messages_[name].emplace(std::move(channel_message)); + } + return; + } + } +#endif // defined(OS_WIN) + + if (peer) { + peer->PortsMessage(std::move(channel_message)); + return; + } + + // If we don't know who the peer is and we are the broker, we can only assume + // the peer is invalid, i.e., it's either a junk name or has already been + // disconnected. + scoped_refptr broker = GetBrokerChannel(); + if (!broker) { + DVLOG(1) << "Dropping message for unknown peer: " << name; + return; + } + + // If we aren't the broker, assume we just need to be introduced and queue + // until that can be either confirmed or denied by the broker. + bool needs_introduction = false; + { + base::AutoLock lock(peers_lock_); + auto& queue = pending_peer_messages_[name]; + needs_introduction = queue.empty(); + queue.emplace(std::move(channel_message)); + } + if (needs_introduction) + broker->RequestIntroduction(name); +} + +void NodeController::AcceptIncomingMessages() { + // This is an impactically large value which should never be reached in + // practice. See the CHECK below for usage. + constexpr size_t kMaxAcceptedMessages = 1000000; + + size_t num_messages_accepted = 0; + while (incoming_messages_flag_) { + // TODO: We may need to be more careful to avoid starving the rest of the + // thread here. Revisit this if it turns out to be a problem. One + // alternative would be to schedule a task to continue pumping messages + // after flushing once. + + messages_lock_.Acquire(); + if (incoming_messages_.empty()) { + messages_lock_.Release(); + break; + } + + // libstdc++'s deque creates an internal buffer on construction, even when + // the size is 0. So avoid creating it until it is necessary. + std::queue messages; + std::swap(messages, incoming_messages_); + incoming_messages_flag_.Set(false); + messages_lock_.Release(); + + num_messages_accepted += messages.size(); + while (!messages.empty()) { + node_->AcceptMessage(std::move(messages.front())); + messages.pop(); + } + + // This is effectively a safeguard against potential bugs which might lead + // to runaway message cycles. If any such cycles arise, we'll start seeing + // crash reports from this location. + CHECK_LE(num_messages_accepted, kMaxAcceptedMessages); + } + + if (num_messages_accepted >= 4) { + // Note: We avoid logging this histogram for the vast majority of cases. + // See https://crbug.com/685763 for more context. + UMA_HISTOGRAM_CUSTOM_COUNTS("Mojo.System.MessagesAcceptedPerEvent", + static_cast(num_messages_accepted), + 1 /* min */, + 500 /* max */, + 50 /* bucket count */); + } + + AttemptShutdownIfRequested(); +} + +void NodeController::ProcessIncomingMessages() { + RequestContext request_context(RequestContext::Source::SYSTEM); + + { + base::AutoLock lock(messages_lock_); + // Allow a new incoming messages processing task to be posted. This can't be + // done after AcceptIncomingMessages() otherwise a message might be missed. + // Doing it here may result in at most two tasks existing at the same time; + // this running one, and one pending in the task runner. + incoming_messages_task_posted_ = false; + } + + AcceptIncomingMessages(); +} + +void NodeController::DropAllPeers() { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + std::vector> all_peers; + { + base::AutoLock lock(parent_lock_); + if (bootstrap_parent_channel_) { + // |bootstrap_parent_channel_| isn't null'd here becuase we rely on its + // existence to determine whether or not this is the root node. Once + // bootstrap_parent_channel_->ShutDown() has been called, + // |bootstrap_parent_channel_| is essentially a dead object and it doesn't + // matter if it's deleted now or when |this| is deleted. + // Note: |bootstrap_parent_channel_| is only modified on the IO thread. + all_peers.push_back(bootstrap_parent_channel_); + } + } + + { + base::AutoLock lock(peers_lock_); + for (const auto& peer : peers_) + all_peers.push_back(peer.second); + for (const auto& peer : pending_children_) + all_peers.push_back(peer.second); + peers_.clear(); + pending_children_.clear(); + pending_peer_messages_.clear(); + peer_connections_.clear(); + } + + for (const auto& peer : all_peers) + peer->ShutDown(); + + if (destroy_on_io_thread_shutdown_) + delete this; +} + +void NodeController::GenerateRandomPortName(ports::PortName* port_name) { + GenerateRandomName(port_name); +} + +void NodeController::AllocMessage(size_t num_header_bytes, + ports::ScopedMessage* message) { + message->reset(new PortsMessage(num_header_bytes, 0, 0, nullptr)); +} + +void NodeController::ForwardMessage(const ports::NodeName& node, + ports::ScopedMessage message) { + DCHECK(message); + bool schedule_pump_task = false; + if (node == name_) { + // NOTE: We need to avoid re-entering the Node instance within + // ForwardMessage. Because ForwardMessage is only ever called + // (synchronously) in response to Node's ClosePort, SendMessage, or + // AcceptMessage, we flush the queue after calling any of those methods. + base::AutoLock lock(messages_lock_); + // |io_task_runner_| may be null in tests or processes that don't require + // multi-process Mojo. + schedule_pump_task = incoming_messages_.empty() && io_task_runner_ && + !incoming_messages_task_posted_; + incoming_messages_task_posted_ |= schedule_pump_task; + incoming_messages_.emplace(std::move(message)); + incoming_messages_flag_.Set(true); + } else { + SendPeerMessage(node, std::move(message)); + } + + if (schedule_pump_task) { + // Normally, the queue is processed after the action that added the local + // message is done (i.e. SendMessage, ClosePort, etc). However, it's also + // possible for a local message to be added as a result of a remote message, + // and OnChannelMessage() doesn't process this queue (although + // OnPortsMessage() does). There may also be other code paths, now or added + // in the future, which cause local messages to be added but don't process + // this message queue. + // + // Instead of adding a call to AcceptIncomingMessages() on every possible + // code path, post a task to the IO thread to process the queue. If the + // current call stack processes the queue, this may end up doing nothing. + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NodeController::ProcessIncomingMessages, + base::Unretained(this))); + } +} + +void NodeController::BroadcastMessage(ports::ScopedMessage message) { + CHECK_EQ(message->num_ports(), 0u); + Channel::MessagePtr channel_message = + static_cast(message.get())->TakeChannelMessage(); + CHECK(!channel_message->has_handles()); + + scoped_refptr broker = GetBrokerChannel(); + if (broker) + broker->Broadcast(std::move(channel_message)); + else + OnBroadcast(name_, std::move(channel_message)); +} + +void NodeController::PortStatusChanged(const ports::PortRef& port) { + scoped_refptr user_data; + node_->GetUserData(port, &user_data); + + PortObserver* observer = static_cast(user_data.get()); + if (observer) { + observer->OnPortStatusChanged(); + } else { + DVLOG(2) << "Ignoring status change for " << port.name() << " because it " + << "doesn't have an observer."; + } +} + +void NodeController::OnAcceptChild(const ports::NodeName& from_node, + const ports::NodeName& parent_name, + const ports::NodeName& token) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + scoped_refptr parent; + { + base::AutoLock lock(parent_lock_); + if (bootstrap_parent_channel_ && parent_name_ == ports::kInvalidNodeName) { + parent_name_ = parent_name; + parent = bootstrap_parent_channel_; + } + } + + if (!parent) { + DLOG(ERROR) << "Unexpected AcceptChild message from " << from_node; + DropPeer(from_node, nullptr); + return; + } + + parent->SetRemoteNodeName(parent_name); + parent->AcceptParent(token, name_); + + // NOTE: The child does not actually add its parent as a peer until + // receiving an AcceptBrokerClient message from the broker. The parent + // will request that said message be sent upon receiving AcceptParent. + + DVLOG(1) << "Child " << name_ << " accepting parent " << parent_name; +} + +void NodeController::OnAcceptParent(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& child_name) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + auto it = pending_children_.find(from_node); + if (it == pending_children_.end() || token != from_node) { + DLOG(ERROR) << "Received unexpected AcceptParent message from " + << from_node; + DropPeer(from_node, nullptr); + return; + } + + { + base::AutoLock lock(reserved_ports_lock_); + auto it = pending_child_tokens_.find(from_node); + if (it != pending_child_tokens_.end()) { + std::string token = std::move(it->second); + pending_child_tokens_.erase(it); + pending_child_tokens_[child_name] = std::move(token); + } + } + + scoped_refptr channel = it->second; + pending_children_.erase(it); + + DCHECK(channel); + + DVLOG(1) << "Parent " << name_ << " accepted child " << child_name; + + AddPeer(child_name, channel, false /* start_channel */); + + // TODO(rockot/amistry): We could simplify child initialization if we could + // synchronously get a new async broker channel from the broker. For now we do + // it asynchronously since it's only used to facilitate handle passing, not + // handle creation. + scoped_refptr broker = GetBrokerChannel(); + if (broker) { + // Inform the broker of this new child. + broker->AddBrokerClient(child_name, channel->CopyRemoteProcessHandle()); + } else { + // If we have no broker, either we need to wait for one, or we *are* the + // broker. + scoped_refptr parent = GetParentChannel(); + if (!parent) { + base::AutoLock lock(parent_lock_); + parent = bootstrap_parent_channel_; + } + + if (!parent) { + // Yes, we're the broker. We can initialize the child directly. + channel->AcceptBrokerClient(name_, ScopedPlatformHandle()); + } else { + // We aren't the broker, so wait for a broker connection. + base::AutoLock lock(broker_lock_); + pending_broker_clients_.push(child_name); + } + } +} + +void NodeController::OnAddBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& client_name, + base::ProcessHandle process_handle) { +#if defined(OS_WIN) + // Scoped handle to avoid leaks on error. + ScopedPlatformHandle scoped_process_handle = + ScopedPlatformHandle(PlatformHandle(process_handle)); +#endif + scoped_refptr sender = GetPeerChannel(from_node); + if (!sender) { + DLOG(ERROR) << "Ignoring AddBrokerClient from unknown sender."; + return; + } + + if (GetPeerChannel(client_name)) { + DLOG(ERROR) << "Ignoring AddBrokerClient for known client."; + DropPeer(from_node, nullptr); + return; + } + + PlatformChannelPair broker_channel; + ConnectionParams connection_params(broker_channel.PassServerHandle()); + scoped_refptr client = + NodeChannel::Create(this, std::move(connection_params), io_task_runner_, + ProcessErrorCallback()); + +#if defined(OS_WIN) + // The broker must have a working handle to the client process in order to + // properly copy other handles to and from the client. + if (!scoped_process_handle.is_valid()) { + DLOG(ERROR) << "Broker rejecting client with invalid process handle."; + return; + } + client->SetRemoteProcessHandle(scoped_process_handle.release().handle); +#else + client->SetRemoteProcessHandle(process_handle); +#endif + + AddPeer(client_name, client, true /* start_channel */); + + DVLOG(1) << "Broker " << name_ << " accepting client " << client_name + << " from peer " << from_node; + + sender->BrokerClientAdded(client_name, broker_channel.PassClientHandle()); +} + +void NodeController::OnBrokerClientAdded(const ports::NodeName& from_node, + const ports::NodeName& client_name, + ScopedPlatformHandle broker_channel) { + scoped_refptr client = GetPeerChannel(client_name); + if (!client) { + DLOG(ERROR) << "BrokerClientAdded for unknown child " << client_name; + return; + } + + // This should have come from our own broker. + if (GetBrokerChannel() != GetPeerChannel(from_node)) { + DLOG(ERROR) << "BrokerClientAdded from non-broker node " << from_node; + return; + } + + DVLOG(1) << "Child " << client_name << " accepted by broker " << from_node; + + client->AcceptBrokerClient(from_node, std::move(broker_channel)); +} + +void NodeController::OnAcceptBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& broker_name, + ScopedPlatformHandle broker_channel) { + // This node should already have a parent in bootstrap mode. + ports::NodeName parent_name; + scoped_refptr parent; + { + base::AutoLock lock(parent_lock_); + parent_name = parent_name_; + parent = bootstrap_parent_channel_; + bootstrap_parent_channel_ = nullptr; + } + DCHECK(parent_name == from_node); + DCHECK(parent); + + std::queue pending_broker_clients; + std::unordered_map + pending_relay_messages; + { + base::AutoLock lock(broker_lock_); + broker_name_ = broker_name; + std::swap(pending_broker_clients, pending_broker_clients_); + std::swap(pending_relay_messages, pending_relay_messages_); + } + DCHECK(broker_name != ports::kInvalidNodeName); + + // It's now possible to add both the broker and the parent as peers. + // Note that the broker and parent may be the same node. + scoped_refptr broker; + if (broker_name == parent_name) { + DCHECK(!broker_channel.is_valid()); + broker = parent; + } else { + DCHECK(broker_channel.is_valid()); + broker = + NodeChannel::Create(this, ConnectionParams(std::move(broker_channel)), + io_task_runner_, ProcessErrorCallback()); + AddPeer(broker_name, broker, true /* start_channel */); + } + + AddPeer(parent_name, parent, false /* start_channel */); + + { + // Complete any port merge requests we have waiting for the parent. + base::AutoLock lock(pending_port_merges_lock_); + for (const auto& request : pending_port_merges_) + parent->RequestPortMerge(request.second.name(), request.first); + pending_port_merges_.clear(); + } + + // Feed the broker any pending children of our own. + while (!pending_broker_clients.empty()) { + const ports::NodeName& child_name = pending_broker_clients.front(); + auto it = pending_children_.find(child_name); + // If for any reason we don't have a pending invitation for the invitee, + // there's nothing left to do: we've already swapped the relevant state into + // the stack. + if (it != pending_children_.end()) { + broker->AddBrokerClient(child_name, + it->second->CopyRemoteProcessHandle()); + } + pending_broker_clients.pop(); + } + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + // Have the broker relay any messages we have waiting. + for (auto& entry : pending_relay_messages) { + const ports::NodeName& destination = entry.first; + auto& message_queue = entry.second; + while (!message_queue.empty()) { + broker->RelayPortsMessage(destination, std::move(message_queue.front())); + message_queue.pop(); + } + } +#endif + + DVLOG(1) << "Child " << name_ << " accepted by broker " << broker_name; +} + +void NodeController::OnPortsMessage(const ports::NodeName& from_node, + Channel::MessagePtr channel_message) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + void* data; + size_t num_data_bytes, num_header_bytes, num_payload_bytes, num_ports_bytes; + if (!ParsePortsMessage(channel_message.get(), &data, &num_data_bytes, + &num_header_bytes, &num_payload_bytes, + &num_ports_bytes)) { + DropPeer(from_node, nullptr); + return; + } + + CHECK(channel_message); + std::unique_ptr ports_message( + new PortsMessage(num_header_bytes, + num_payload_bytes, + num_ports_bytes, + std::move(channel_message))); + ports_message->set_source_node(from_node); + node_->AcceptMessage(ports::ScopedMessage(ports_message.release())); + AcceptIncomingMessages(); +} + +void NodeController::OnRequestPortMerge( + const ports::NodeName& from_node, + const ports::PortName& connector_port_name, + const std::string& token) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + DVLOG(2) << "Node " << name_ << " received RequestPortMerge for token " + << token << " and port " << connector_port_name << "@" << from_node; + + ports::PortRef local_port; + { + base::AutoLock lock(reserved_ports_lock_); + auto it = reserved_ports_.find(token); + if (it == reserved_ports_.end()) { + DVLOG(1) << "Ignoring request to connect to port for unknown token " + << token; + return; + } + local_port = it->second.port; + reserved_ports_.erase(it); + } + + int rv = node_->MergePorts(local_port, from_node, connector_port_name); + if (rv != ports::OK) + DLOG(ERROR) << "MergePorts failed: " << rv; + + AcceptIncomingMessages(); +} + +void NodeController::OnRequestIntroduction(const ports::NodeName& from_node, + const ports::NodeName& name) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + scoped_refptr requestor = GetPeerChannel(from_node); + if (from_node == name || name == ports::kInvalidNodeName || !requestor) { + DLOG(ERROR) << "Rejecting invalid OnRequestIntroduction message from " + << from_node; + DropPeer(from_node, nullptr); + return; + } + + scoped_refptr new_friend = GetPeerChannel(name); + if (!new_friend) { + // We don't know who they're talking about! + requestor->Introduce(name, ScopedPlatformHandle()); + } else { + PlatformChannelPair new_channel; + requestor->Introduce(name, new_channel.PassServerHandle()); + new_friend->Introduce(from_node, new_channel.PassClientHandle()); + } +} + +void NodeController::OnIntroduce(const ports::NodeName& from_node, + const ports::NodeName& name, + ScopedPlatformHandle channel_handle) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + if (!channel_handle.is_valid()) { + node_->LostConnectionToNode(name); + + DVLOG(1) << "Could not be introduced to peer " << name; + base::AutoLock lock(peers_lock_); + pending_peer_messages_.erase(name); + return; + } + + scoped_refptr channel = + NodeChannel::Create(this, ConnectionParams(std::move(channel_handle)), + io_task_runner_, ProcessErrorCallback()); + + DVLOG(1) << "Adding new peer " << name << " via parent introduction."; + AddPeer(name, channel, true /* start_channel */); +} + +void NodeController::OnBroadcast(const ports::NodeName& from_node, + Channel::MessagePtr message) { + DCHECK(!message->has_handles()); + + void* data; + size_t num_data_bytes, num_header_bytes, num_payload_bytes, num_ports_bytes; + if (!ParsePortsMessage(message.get(), &data, &num_data_bytes, + &num_header_bytes, &num_payload_bytes, + &num_ports_bytes)) { + DropPeer(from_node, nullptr); + return; + } + + // Broadcast messages must not contain ports. + if (num_ports_bytes > 0) { + DropPeer(from_node, nullptr); + return; + } + + base::AutoLock lock(peers_lock_); + for (auto& iter : peers_) { + // Copy and send the message to each known peer. + Channel::MessagePtr peer_message( + new Channel::Message(message->payload_size(), 0)); + memcpy(peer_message->mutable_payload(), message->payload(), + message->payload_size()); + iter.second->PortsMessage(std::move(peer_message)); + } +} + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) +void NodeController::OnRelayPortsMessage(const ports::NodeName& from_node, + base::ProcessHandle from_process, + const ports::NodeName& destination, + Channel::MessagePtr message) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + if (GetBrokerChannel()) { + // Only the broker should be asked to relay a message. + LOG(ERROR) << "Non-broker refusing to relay message."; + DropPeer(from_node, nullptr); + return; + } + + // The parent should always know which process this came from. + DCHECK(from_process != base::kNullProcessHandle); + +#if defined(OS_WIN) + // Rewrite the handles to this (the parent) process. If the message is + // destined for another child process, the handles will be rewritten to that + // process before going out (see NodeChannel::WriteChannelMessage). + // + // TODO: We could avoid double-duplication. + // + // Note that we explicitly mark the handles as being owned by the sending + // process before rewriting them, in order to accommodate RewriteHandles' + // internal sanity checks. + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + for (size_t i = 0; i < handles->size(); ++i) + (*handles)[i].owning_process = from_process; + if (!Channel::Message::RewriteHandles(from_process, + base::GetCurrentProcessHandle(), + handles.get())) { + DLOG(ERROR) << "Failed to relay one or more handles."; + } + message->SetHandles(std::move(handles)); +#else + MachPortRelay* relay = GetMachPortRelay(); + if (!relay) { + LOG(ERROR) << "Receiving Mach ports without a port relay from " + << from_node << ". Dropping message."; + return; + } + if (!relay->ExtractPortRights(message.get(), from_process)) { + // NodeChannel should ensure that MachPortRelay is ready for the remote + // process. At this point, if the port extraction failed, either something + // went wrong in the mach stuff, or the remote process died. + LOG(ERROR) << "Error on receiving Mach ports " << from_node + << ". Dropping message."; + return; + } +#endif // defined(OS_WIN) + + if (destination == name_) { + // Great, we can deliver this message locally. + OnPortsMessage(from_node, std::move(message)); + return; + } + + scoped_refptr peer = GetPeerChannel(destination); + if (peer) + peer->PortsMessageFromRelay(from_node, std::move(message)); + else + DLOG(ERROR) << "Dropping relay message for unknown node " << destination; +} + +void NodeController::OnPortsMessageFromRelay(const ports::NodeName& from_node, + const ports::NodeName& source_node, + Channel::MessagePtr message) { + if (GetPeerChannel(from_node) != GetBrokerChannel()) { + LOG(ERROR) << "Refusing relayed message from non-broker node."; + DropPeer(from_node, nullptr); + return; + } + + OnPortsMessage(source_node, std::move(message)); +} +#endif + +void NodeController::OnAcceptPeer(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& peer_name, + const ports::PortName& port_name) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + auto it = peer_connections_.find(from_node); + if (it == peer_connections_.end()) { + DLOG(ERROR) << "Received unexpected AcceptPeer message from " << from_node; + DropPeer(from_node, nullptr); + return; + } + + scoped_refptr channel = std::move(it->second.channel); + ports::PortRef local_port = it->second.local_port; + std::string peer_token = std::move(it->second.peer_token); + peer_connections_.erase(it); + DCHECK(channel); + + // If the peer connection is a self connection (which is used in tests), + // drop the channel to it and skip straight to merging the ports. + if (name_ == peer_name) { + peers_by_token_.erase(peer_token); + } else { + peers_by_token_[peer_token] = peer_name; + peer_connections_.insert( + {peer_name, PeerConnection{nullptr, local_port, peer_token}}); + DVLOG(1) << "Node " << name_ << " accepted peer " << peer_name; + + AddPeer(peer_name, channel, false /* start_channel */); + } + + // We need to choose one side to initiate the port merge. It doesn't matter + // who does it as long as they don't both try. Simple solution: pick the one + // with the "smaller" port name. + if (local_port.name() < port_name) { + node()->MergePorts(local_port, peer_name, port_name); + } +} + +void NodeController::OnChannelError(const ports::NodeName& from_node, + NodeChannel* channel) { + if (io_task_runner_->RunsTasksOnCurrentThread()) { + DropPeer(from_node, channel); + // DropPeer may have caused local port closures, so be sure to process any + // pending local messages. + AcceptIncomingMessages(); + } else { + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NodeController::OnChannelError, base::Unretained(this), + from_node, channel)); + } +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +MachPortRelay* NodeController::GetMachPortRelay() { + { + base::AutoLock lock(parent_lock_); + // Return null if we're not the root. + if (bootstrap_parent_channel_ || parent_name_ != ports::kInvalidNodeName) + return nullptr; + } + + base::AutoLock lock(mach_port_relay_lock_); + return mach_port_relay_.get(); +} +#endif + +void NodeController::CancelPendingPortMerges() { + std::vector ports_to_close; + + { + base::AutoLock lock(pending_port_merges_lock_); + reject_pending_merges_ = true; + for (const auto& port : pending_port_merges_) + ports_to_close.push_back(port.second); + pending_port_merges_.clear(); + } + + for (const auto& port : ports_to_close) + node_->ClosePort(port); +} + +void NodeController::DestroyOnIOThreadShutdown() { + destroy_on_io_thread_shutdown_ = true; +} + +void NodeController::AttemptShutdownIfRequested() { + if (!shutdown_callback_flag_) + return; + + base::Closure callback; + { + base::AutoLock lock(shutdown_lock_); + if (shutdown_callback_.is_null()) + return; + if (!node_->CanShutdownCleanly( + ports::Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)) { + DVLOG(2) << "Unable to cleanly shut down node " << name_; + return; + } + + callback = shutdown_callback_; + shutdown_callback_.Reset(); + shutdown_callback_flag_.Set(false); + } + + DCHECK(!callback.is_null()); + + callback.Run(); +} + +NodeController::PeerConnection::PeerConnection() = default; + +NodeController::PeerConnection::PeerConnection( + const PeerConnection& other) = default; + +NodeController::PeerConnection::PeerConnection( + PeerConnection&& other) = default; + +NodeController::PeerConnection::PeerConnection( + scoped_refptr channel, + const ports::PortRef& local_port, + const std::string& peer_token) + : channel(std::move(channel)), + local_port(local_port), + peer_token(peer_token) {} + +NodeController::PeerConnection::~PeerConnection() = default; + +NodeController::PeerConnection& NodeController::PeerConnection:: +operator=(const PeerConnection& other) = default; + +NodeController::PeerConnection& NodeController::PeerConnection:: +operator=(PeerConnection&& other) = default; + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/node_controller.h b/mojo/edk/system/node_controller.h new file mode 100644 index 0000000000000000000000000000000000000000..46a2d612085cd2983ff4c00255d3fed906ca9a7d --- /dev/null +++ b/mojo/edk/system/node_controller.h @@ -0,0 +1,378 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_NODE_CONTROLLER_H_ +#define MOJO_EDK_SYSTEM_NODE_CONTROLLER_H_ + +#include +#include +#include +#include +#include +#include + +#include "base/callback.h" +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/task_runner.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/atomic_flag.h" +#include "mojo/edk/system/node_channel.h" +#include "mojo/edk/system/ports/name.h" +#include "mojo/edk/system/ports/node.h" +#include "mojo/edk/system/ports/node_delegate.h" + +namespace base { +class PortProvider; +} + +namespace mojo { +namespace edk { + +class Broker; +class Core; +class MachPortRelay; +class PortsMessage; + +// The owner of ports::Node which facilitates core EDK implementation. All +// public interface methods are safe to call from any thread. +class NodeController : public ports::NodeDelegate, + public NodeChannel::Delegate { + public: + class PortObserver : public ports::UserData { + public: + virtual void OnPortStatusChanged() = 0; + + protected: + ~PortObserver() override {} + }; + + // |core| owns and out-lives us. + explicit NodeController(Core* core); + ~NodeController() override; + + const ports::NodeName& name() const { return name_; } + Core* core() const { return core_; } + ports::Node* node() const { return node_.get(); } + scoped_refptr io_task_runner() const { + return io_task_runner_; + } + +#if defined(OS_MACOSX) && !defined(OS_IOS) + // Create the relay used to transfer mach ports between processes. + void CreateMachPortRelay(base::PortProvider* port_provider); +#endif + + // Called exactly once, shortly after construction, and before any other + // methods are called on this object. + void SetIOTaskRunner(scoped_refptr io_task_runner); + + // Connects this node to a child node. This node will initiate a handshake. + void ConnectToChild(base::ProcessHandle process_handle, + ConnectionParams connection_params, + const std::string& child_token, + const ProcessErrorCallback& process_error_callback); + + // Closes all reserved ports which associated with the child process + // |child_token|. + void CloseChildPorts(const std::string& child_token); + + // Close a connection to a peer associated with |peer_token|. + void ClosePeerConnection(const std::string& peer_token); + + // Connects this node to a parent node. The parent node will initiate a + // handshake. + void ConnectToParent(ConnectionParams connection_params); + + // Connects this node to a peer node. On success, |port| will be merged with + // the corresponding port in the peer node. + void ConnectToPeer(ConnectionParams connection_params, + const ports::PortRef& port, + const std::string& peer_token); + + // Sets a port's observer. If |observer| is null the port's current observer + // is removed. + void SetPortObserver(const ports::PortRef& port, + scoped_refptr observer); + + // Closes a port. Use this in lieu of calling Node::ClosePort() directly, as + // it ensures the port's observer has also been removed. + void ClosePort(const ports::PortRef& port); + + // Sends a message on a port to its peer. + int SendMessage(const ports::PortRef& port_ref, + std::unique_ptr message); + + // Reserves a local port |port| associated with |token|. A peer holding a copy + // of |token| can merge one of its own ports into this one. + void ReservePort(const std::string& token, const ports::PortRef& port, + const std::string& child_token); + + // Merges a local port |port| into a port reserved by |token| in the parent. + void MergePortIntoParent(const std::string& token, + const ports::PortRef& port); + + // Merges two local ports together. + int MergeLocalPorts(const ports::PortRef& port0, const ports::PortRef& port1); + + // Creates a new shared buffer for use in the current process. + scoped_refptr CreateSharedBuffer(size_t num_bytes); + + // Request that the Node be shut down cleanly. This may take an arbitrarily + // long time to complete, at which point |callback| will be called. + // + // Note that while it is safe to continue using the NodeController's public + // interface after requesting shutdown, you do so at your own risk and there + // is NO guarantee that new messages will be sent or ports will complete + // transfer. + void RequestShutdown(const base::Closure& callback); + + // Notifies the NodeController that we received a bad message from the given + // node. + void NotifyBadMessageFrom(const ports::NodeName& source_node, + const std::string& error); + + private: + friend Core; + + using NodeMap = std::unordered_map>; + using OutgoingMessageQueue = std::queue; + + struct ReservedPort { + ports::PortRef port; + const std::string child_token; + }; + + struct PeerConnection { + PeerConnection(); + PeerConnection(const PeerConnection& other); + PeerConnection(PeerConnection&& other); + PeerConnection(scoped_refptr channel, + const ports::PortRef& local_port, + const std::string& peer_token); + ~PeerConnection(); + + PeerConnection& operator=(const PeerConnection& other); + PeerConnection& operator=(PeerConnection&& other); + + + scoped_refptr channel; + ports::PortRef local_port; + std::string peer_token; + }; + + void ConnectToChildOnIOThread( + base::ProcessHandle process_handle, + ConnectionParams connection_params, + ports::NodeName token, + const ProcessErrorCallback& process_error_callback); + void ConnectToParentOnIOThread(ConnectionParams connection_params); + + void ConnectToPeerOnIOThread(ConnectionParams connection_params, + ports::NodeName token, + ports::PortRef port, + const std::string& peer_token); + void ClosePeerConnectionOnIOThread(const std::string& node_name); + + scoped_refptr GetPeerChannel(const ports::NodeName& name); + scoped_refptr GetParentChannel(); + scoped_refptr GetBrokerChannel(); + + void AddPeer(const ports::NodeName& name, + scoped_refptr channel, + bool start_channel); + void DropPeer(const ports::NodeName& name, NodeChannel* channel); + void SendPeerMessage(const ports::NodeName& name, + ports::ScopedMessage message); + void AcceptIncomingMessages(); + void ProcessIncomingMessages(); + void DropAllPeers(); + + // ports::NodeDelegate: + void GenerateRandomPortName(ports::PortName* port_name) override; + void AllocMessage(size_t num_header_bytes, + ports::ScopedMessage* message) override; + void ForwardMessage(const ports::NodeName& node, + ports::ScopedMessage message) override; + void BroadcastMessage(ports::ScopedMessage message) override; + void PortStatusChanged(const ports::PortRef& port) override; + + // NodeChannel::Delegate: + void OnAcceptChild(const ports::NodeName& from_node, + const ports::NodeName& parent_name, + const ports::NodeName& token) override; + void OnAcceptParent(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& child_name) override; + void OnAddBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& client_name, + base::ProcessHandle process_handle) override; + void OnBrokerClientAdded(const ports::NodeName& from_node, + const ports::NodeName& client_name, + ScopedPlatformHandle broker_channel) override; + void OnAcceptBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& broker_name, + ScopedPlatformHandle broker_channel) override; + void OnPortsMessage(const ports::NodeName& from_node, + Channel::MessagePtr message) override; + void OnRequestPortMerge(const ports::NodeName& from_node, + const ports::PortName& connector_port_name, + const std::string& token) override; + void OnRequestIntroduction(const ports::NodeName& from_node, + const ports::NodeName& name) override; + void OnIntroduce(const ports::NodeName& from_node, + const ports::NodeName& name, + ScopedPlatformHandle channel_handle) override; + void OnBroadcast(const ports::NodeName& from_node, + Channel::MessagePtr message) override; +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + void OnRelayPortsMessage(const ports::NodeName& from_node, + base::ProcessHandle from_process, + const ports::NodeName& destination, + Channel::MessagePtr message) override; + void OnPortsMessageFromRelay(const ports::NodeName& from_node, + const ports::NodeName& source_node, + Channel::MessagePtr message) override; +#endif + void OnAcceptPeer(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& peer_name, + const ports::PortName& port_name) override; + void OnChannelError(const ports::NodeName& from_node, + NodeChannel* channel) override; +#if defined(OS_MACOSX) && !defined(OS_IOS) + MachPortRelay* GetMachPortRelay() override; +#endif + + // Cancels all pending port merges. These are merges which are supposed to + // be requested from the parent ASAP, and they may be cancelled if the + // connection to the parent is broken or never established. + void CancelPendingPortMerges(); + + // Marks this NodeController for destruction when the IO thread shuts down. + // This is used in case Core is torn down before the IO thread. Must only be + // called on the IO thread. + void DestroyOnIOThreadShutdown(); + + // If there is a registered shutdown callback (meaning shutdown has been + // requested, this checks the Node's status to see if clean shutdown is + // possible. If so, shutdown is performed and the shutdown callback is run. + void AttemptShutdownIfRequested(); + + // These are safe to access from any thread as long as the Node is alive. + Core* const core_; + const ports::NodeName name_; + const std::unique_ptr node_; + scoped_refptr io_task_runner_; + + // Guards |peers_| and |pending_peer_messages_|. + base::Lock peers_lock_; + + // Channels to known peers, including parent and children, if any. + NodeMap peers_; + + // Outgoing message queues for peers we've heard of but can't yet talk to. + std::unordered_map + pending_peer_messages_; + + // Guards |reserved_ports_| and |pending_child_tokens_|. + base::Lock reserved_ports_lock_; + + // Ports reserved by token. Key is the port token. + base::hash_map reserved_ports_; + // TODO(amistry): This _really_ needs to be a bimap. Unfortunately, we don't + // have one yet :( + std::unordered_map pending_child_tokens_; + + // Guards |pending_port_merges_| and |reject_pending_merges_|. + base::Lock pending_port_merges_lock_; + + // A set of port merge requests awaiting parent connection. + std::vector> pending_port_merges_; + + // Indicates that new merge requests should be rejected because the parent has + // disconnected. + bool reject_pending_merges_ = false; + + // Guards |parent_name_| and |bootstrap_parent_channel_|. + base::Lock parent_lock_; + + // The name of our parent node, if any. + ports::NodeName parent_name_; + + // A temporary reference to the parent channel before we know their name. + scoped_refptr bootstrap_parent_channel_; + + // Guards |broker_name_|, |pending_broker_clients_|, and + // |pending_relay_messages_|. + base::Lock broker_lock_; + + // The name of our broker node, if any. + ports::NodeName broker_name_; + + // A queue of pending child names waiting to be connected to a broker. + std::queue pending_broker_clients_; + + // Messages waiting to be relayed by the broker once it's known. + std::unordered_map + pending_relay_messages_; + + // Guards |incoming_messages_| and |incoming_messages_task_posted_|. + base::Lock messages_lock_; + std::queue incoming_messages_; + // Ensures that there is only one incoming messages task posted to the IO + // thread. + bool incoming_messages_task_posted_ = false; + // Flag to fast-path checking |incoming_messages_|. + AtomicFlag incoming_messages_flag_; + + // Guards |shutdown_callback_|. + base::Lock shutdown_lock_; + + // Set by RequestShutdown(). If this is non-null, the controller will + // begin polling the Node to see if clean shutdown is possible any time the + // Node's state is modified by the controller. + base::Closure shutdown_callback_; + // Flag to fast-path checking |shutdown_callback_|. + AtomicFlag shutdown_callback_flag_; + + // All other fields below must only be accessed on the I/O thread, i.e., the + // thread on which core_->io_task_runner() runs tasks. + + // Channels to children during handshake. + NodeMap pending_children_; + + using PeerNodeMap = + std::unordered_map; + PeerNodeMap peer_connections_; + + // Maps from peer token to node name, pending or not. + std::unordered_map peers_by_token_; + + // Indicates whether this object should delete itself on IO thread shutdown. + // Must only be accessed from the IO thread. + bool destroy_on_io_thread_shutdown_ = false; + +#if !defined(OS_MACOSX) && !defined(OS_NACL_SFI) + // Broker for sync shared buffer creation in children. + std::unique_ptr broker_; +#endif + +#if defined(OS_MACOSX) && !defined(OS_IOS) + base::Lock mach_port_relay_lock_; + // Relay for transferring mach ports to/from children. + std::unique_ptr mach_port_relay_; +#endif + + DISALLOW_COPY_AND_ASSIGN(NodeController); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_NODE_CONTROLLER_H_ diff --git a/mojo/edk/system/options_validation.h b/mojo/edk/system/options_validation.h new file mode 100644 index 0000000000000000000000000000000000000000..e1b337d5f744e34f7cad8e12c3b9ff356247a8fd --- /dev/null +++ b/mojo/edk/system/options_validation.h @@ -0,0 +1,97 @@ +// Copyright 2014 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. + +// Functions to help with verifying various |Mojo...Options| structs from the +// (public, C) API. These are "extensible" structs, which all have |struct_size| +// as their first member. All fields (other than |struct_size|) are optional, +// but any |flags| specified must be known to the system (otherwise, an error of +// |MOJO_RESULT_UNIMPLEMENTED| should be returned). + +#ifndef MOJO_EDK_SYSTEM_OPTIONS_VALIDATION_H_ +#define MOJO_EDK_SYSTEM_OPTIONS_VALIDATION_H_ + +#include +#include + +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { +namespace edk { + +template +class UserOptionsReader { + public: + // Constructor from a |const* Options| (which it checks -- this constructor + // has side effects!). + // Note: We initialize |options_reader_| without checking, since we do a check + // in |GetSizeForReader()|. + explicit UserOptionsReader(const Options* options) { + CHECK(options && IsAligned(options)); + options_ = GetSizeForReader(options) == 0 ? nullptr : options; + static_assert(offsetof(Options, struct_size) == 0, + "struct_size not first member of Options"); + // TODO(vtl): Enable when MSVC supports this (C++11 extended sizeof): + // static_assert(sizeof(Options::struct_size) == sizeof(uint32_t), + // "Options::struct_size not a uint32_t"); + // (Or maybe assert that its type is uint32_t?) + } + + bool is_valid() const { return !!options_; } + + const Options& options() const { + DCHECK(is_valid()); + return *options_; + } + + // Checks that the given (variable-size) |options| passed to the constructor + // (plausibly) has a member at the given offset with the given size. You + // probably want to use |OPTIONS_STRUCT_HAS_MEMBER()| instead. + bool HasMember(size_t offset, size_t size) const { + DCHECK(is_valid()); + // We assume that |offset| and |size| are reasonable, since they should come + // from |offsetof(Options, some_member)| and |sizeof(Options::some_member)|, + // respectively. + return options().struct_size >= offset + size; + } + + private: + static inline size_t GetSizeForReader(const Options* options) { + uint32_t struct_size = *reinterpret_cast(options); + if (struct_size < sizeof(uint32_t)) + return 0; + + return std::min(static_cast(struct_size), sizeof(Options)); + } + + template + static bool IsAligned(const void* pointer) { + return reinterpret_cast(pointer) % alignment == 0; + } + + const Options* options_; + + DISALLOW_COPY_AND_ASSIGN(UserOptionsReader); +}; + +// Macro to invoke |UserOptionsReader::HasMember()| parametrized by +// member name instead of offset and size. +// +// (We can't just give |HasMember()| a member pointer template argument instead, +// since there's no good/strictly-correct way to get an offset from that.) +// +// TODO(vtl): With C++11, use |sizeof(Options::member)| instead of (the +// contortion below). We might also be able to pull out the type |Options| from +// |reader| (using |decltype|) instead of requiring a parameter. +#define OPTIONS_STRUCT_HAS_MEMBER(Options, member, reader) \ + reader.HasMember(offsetof(Options, member), sizeof(reader.options().member)) + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_OPTIONS_VALIDATION_H_ diff --git a/mojo/edk/system/options_validation_unittest.cc b/mojo/edk/system/options_validation_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..a01a92cfb10544ebda99125acadc61cd29498232 --- /dev/null +++ b/mojo/edk/system/options_validation_unittest.cc @@ -0,0 +1,134 @@ +// Copyright 2014 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. + +#include "mojo/edk/system/options_validation.h" + +#include +#include + +#include "mojo/public/c/system/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +// Declare a test options struct just as we do in actual public headers. + +using TestOptionsFlags = uint32_t; + +static_assert(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment"); +struct MOJO_ALIGNAS(8) TestOptions { + uint32_t struct_size; + TestOptionsFlags flags; + uint32_t member1; + uint32_t member2; +}; +static_assert(sizeof(TestOptions) == 16, "TestOptions has wrong size"); + +const uint32_t kSizeOfTestOptions = static_cast(sizeof(TestOptions)); + +TEST(OptionsValidationTest, Valid) { + { + const TestOptions kOptions = {kSizeOfTestOptions}; + UserOptionsReader reader(&kOptions); + EXPECT_TRUE(reader.is_valid()); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader)); + } + { + const TestOptions kOptions = {static_cast( + offsetof(TestOptions, struct_size) + sizeof(uint32_t))}; + UserOptionsReader reader(&kOptions); + EXPECT_TRUE(reader.is_valid()); + EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader)); + EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader)); + EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader)); + } + + { + const TestOptions kOptions = { + static_cast(offsetof(TestOptions, flags) + sizeof(uint32_t))}; + UserOptionsReader reader(&kOptions); + EXPECT_TRUE(reader.is_valid()); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader)); + EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader)); + EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader)); + } + { + MOJO_ALIGNAS(8) char buf[sizeof(TestOptions) + 100] = {}; + TestOptions* options = reinterpret_cast(buf); + options->struct_size = kSizeOfTestOptions + 1; + UserOptionsReader reader(options); + EXPECT_TRUE(reader.is_valid()); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader)); + } + { + MOJO_ALIGNAS(8) char buf[sizeof(TestOptions) + 100] = {}; + TestOptions* options = reinterpret_cast(buf); + options->struct_size = kSizeOfTestOptions + 4; + UserOptionsReader reader(options); + EXPECT_TRUE(reader.is_valid()); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader)); + } +} + +TEST(OptionsValidationTest, Invalid) { + // Size too small: + for (size_t i = 0; i < sizeof(uint32_t); i++) { + TestOptions options = {static_cast(i)}; + UserOptionsReader reader(&options); + EXPECT_FALSE(reader.is_valid()) << i; + } +} + +// These test invalid arguments that should cause death if we're being paranoid +// about checking arguments (which we would want to do if, e.g., we were in a +// true "kernel" situation, but we might not want to do otherwise for +// performance reasons). Probably blatant errors like passing in null pointers +// (for required pointer arguments) will still cause death, but perhaps not +// predictably. +TEST(OptionsValidationTest, InvalidDeath) { +#if defined(OFFICIAL_BUILD) + const char kMemoryCheckFailedRegex[] = ""; +#else + const char kMemoryCheckFailedRegex[] = "Check failed"; +#endif + + // Null: + EXPECT_DEATH_IF_SUPPORTED( + { UserOptionsReader reader((nullptr)); }, + kMemoryCheckFailedRegex); + + // Unaligned: + EXPECT_DEATH_IF_SUPPORTED( + { + UserOptionsReader reader( + reinterpret_cast(1)); + }, + kMemoryCheckFailedRegex); + // Note: The current implementation checks the size only after checking the + // alignment versus that required for the |uint32_t| size, so it won't die in + // the expected way if you pass, e.g., 4. So we have to manufacture a valid + // pointer at an offset of alignment 4. + EXPECT_DEATH_IF_SUPPORTED( + { + uint32_t buffer[100] = {}; + TestOptions* options = (reinterpret_cast(buffer) % 8 == 0) + ? reinterpret_cast(&buffer[1]) + : reinterpret_cast(&buffer[0]); + options->struct_size = static_cast(sizeof(TestOptions)); + UserOptionsReader reader(options); + }, + kMemoryCheckFailedRegex); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/platform_handle_dispatcher.cc b/mojo/edk/system/platform_handle_dispatcher.cc new file mode 100644 index 0000000000000000000000000000000000000000..3e708c25174f322b52606f3d6c3def1ad465dc8f --- /dev/null +++ b/mojo/edk/system/platform_handle_dispatcher.cc @@ -0,0 +1,104 @@ +// Copyright 2014 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. + +#include "mojo/edk/system/platform_handle_dispatcher.h" + +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/platform_handle_vector.h" + +namespace mojo { +namespace edk { + +// static +scoped_refptr PlatformHandleDispatcher::Create( + ScopedPlatformHandle platform_handle) { + return new PlatformHandleDispatcher(std::move(platform_handle)); +} + +ScopedPlatformHandle PlatformHandleDispatcher::PassPlatformHandle() { + return std::move(platform_handle_); +} + +Dispatcher::Type PlatformHandleDispatcher::GetType() const { + return Type::PLATFORM_HANDLE; +} + +MojoResult PlatformHandleDispatcher::Close() { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + is_closed_ = true; + platform_handle_.reset(); + return MOJO_RESULT_OK; +} + +void PlatformHandleDispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) { + *num_bytes = 0; + *num_ports = 0; + *num_handles = 1; +} + +bool PlatformHandleDispatcher::EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) { + base::AutoLock lock(lock_); + if (is_closed_) + return false; + handles[0] = platform_handle_.get(); + return true; +} + +bool PlatformHandleDispatcher::BeginTransit() { + base::AutoLock lock(lock_); + if (in_transit_) + return false; + in_transit_ = !is_closed_; + return in_transit_; +} + +void PlatformHandleDispatcher::CompleteTransitAndClose() { + base::AutoLock lock(lock_); + + in_transit_ = false; + is_closed_ = true; + + // The system has taken ownership of our handle. + ignore_result(platform_handle_.release()); +} + +void PlatformHandleDispatcher::CancelTransit() { + base::AutoLock lock(lock_); + in_transit_ = false; +} + +// static +scoped_refptr PlatformHandleDispatcher::Deserialize( + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles) { + if (num_bytes || num_ports || num_handles != 1) + return nullptr; + + PlatformHandle handle; + std::swap(handle, handles[0]); + + return PlatformHandleDispatcher::Create(ScopedPlatformHandle(handle)); +} + +PlatformHandleDispatcher::PlatformHandleDispatcher( + ScopedPlatformHandle platform_handle) + : platform_handle_(std::move(platform_handle)) {} + +PlatformHandleDispatcher::~PlatformHandleDispatcher() { + DCHECK(is_closed_ && !in_transit_); + DCHECK(!platform_handle_.is_valid()); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/platform_handle_dispatcher.h b/mojo/edk/system/platform_handle_dispatcher.h new file mode 100644 index 0000000000000000000000000000000000000000..a36c7a0e22f4fa4fa4e989962d10e8dcacc18f1f --- /dev/null +++ b/mojo/edk/system/platform_handle_dispatcher.h @@ -0,0 +1,61 @@ +// Copyright 2014 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 MOJO_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +class MOJO_SYSTEM_IMPL_EXPORT PlatformHandleDispatcher : public Dispatcher { + public: + static scoped_refptr Create( + ScopedPlatformHandle platform_handle); + + ScopedPlatformHandle PassPlatformHandle(); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) override; + bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) override; + bool BeginTransit() override; + void CompleteTransitAndClose() override; + void CancelTransit() override; + + static scoped_refptr Deserialize( + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles); + + private: + PlatformHandleDispatcher(ScopedPlatformHandle platform_handle); + ~PlatformHandleDispatcher() override; + + base::Lock lock_; + bool in_transit_ = false; + bool is_closed_ = false; + ScopedPlatformHandle platform_handle_; + + DISALLOW_COPY_AND_ASSIGN(PlatformHandleDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_ diff --git a/mojo/edk/system/platform_handle_dispatcher_unittest.cc b/mojo/edk/system/platform_handle_dispatcher_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..7a942622b0b92c05c52eac620beaa716715466b0 --- /dev/null +++ b/mojo/edk/system/platform_handle_dispatcher_unittest.cc @@ -0,0 +1,123 @@ +// Copyright 2014 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. + +#include "mojo/edk/system/platform_handle_dispatcher.h" + +#include +#include + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ref_counted.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/test/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +TEST(PlatformHandleDispatcherTest, Basic) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + static const char kHelloWorld[] = "hello world"; + + base::FilePath unused; + base::ScopedFILE fp( + CreateAndOpenTemporaryFileInDir(temp_dir.GetPath(), &unused)); + ASSERT_TRUE(fp); + EXPECT_EQ(sizeof(kHelloWorld), + fwrite(kHelloWorld, 1, sizeof(kHelloWorld), fp.get())); + + ScopedPlatformHandle h(test::PlatformHandleFromFILE(std::move(fp))); + EXPECT_FALSE(fp); + ASSERT_TRUE(h.is_valid()); + + scoped_refptr dispatcher = + PlatformHandleDispatcher::Create(std::move(h)); + EXPECT_FALSE(h.is_valid()); + EXPECT_EQ(Dispatcher::Type::PLATFORM_HANDLE, dispatcher->GetType()); + + h = dispatcher->PassPlatformHandle(); + EXPECT_TRUE(h.is_valid()); + + fp = test::FILEFromPlatformHandle(std::move(h), "rb"); + EXPECT_FALSE(h.is_valid()); + EXPECT_TRUE(fp); + + rewind(fp.get()); + char read_buffer[1000] = {}; + EXPECT_EQ(sizeof(kHelloWorld), + fread(read_buffer, 1, sizeof(read_buffer), fp.get())); + EXPECT_STREQ(kHelloWorld, read_buffer); + + // Try getting the handle again. (It should fail cleanly.) + h = dispatcher->PassPlatformHandle(); + EXPECT_FALSE(h.is_valid()); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); +} + +TEST(PlatformHandleDispatcherTest, Serialization) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + static const char kFooBar[] = "foo bar"; + + base::FilePath unused; + base::ScopedFILE fp( + CreateAndOpenTemporaryFileInDir(temp_dir.GetPath(), &unused)); + EXPECT_EQ(sizeof(kFooBar), fwrite(kFooBar, 1, sizeof(kFooBar), fp.get())); + + scoped_refptr dispatcher = + PlatformHandleDispatcher::Create( + test::PlatformHandleFromFILE(std::move(fp))); + + uint32_t num_bytes = 0; + uint32_t num_ports = 0; + uint32_t num_handles = 0; + EXPECT_TRUE(dispatcher->BeginTransit()); + dispatcher->StartSerialize(&num_bytes, &num_ports, &num_handles); + + EXPECT_EQ(0u, num_bytes); + EXPECT_EQ(0u, num_ports); + EXPECT_EQ(1u, num_handles); + + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector(1)); + EXPECT_TRUE(dispatcher->EndSerialize(nullptr, nullptr, handles->data())); + dispatcher->CompleteTransitAndClose(); + + EXPECT_TRUE(handles->at(0).is_valid()); + + ScopedPlatformHandle handle = dispatcher->PassPlatformHandle(); + EXPECT_FALSE(handle.is_valid()); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, dispatcher->Close()); + + dispatcher = static_cast( + Dispatcher::Deserialize(Dispatcher::Type::PLATFORM_HANDLE, nullptr, + num_bytes, nullptr, num_ports, handles->data(), + 1).get()); + + EXPECT_FALSE(handles->at(0).is_valid()); + EXPECT_TRUE(dispatcher->GetType() == Dispatcher::Type::PLATFORM_HANDLE); + + fp = test::FILEFromPlatformHandle(dispatcher->PassPlatformHandle(), "rb"); + EXPECT_TRUE(fp); + + rewind(fp.get()); + char read_buffer[1000] = {}; + EXPECT_EQ(sizeof(kFooBar), + fread(read_buffer, 1, sizeof(read_buffer), fp.get())); + EXPECT_STREQ(kFooBar, read_buffer); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/platform_wrapper_unittest.cc b/mojo/edk/system/platform_wrapper_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..f29d62b04fb03d481d48066908a5fb163422354c --- /dev/null +++ b/mojo/edk/system/platform_wrapper_unittest.cc @@ -0,0 +1,212 @@ +// Copyright 2016 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. + +#include +#include + +#include +#include +#include + +#include "base/files/file.h" +#include "base/files/file_util.h" +#include "base/memory/shared_memory.h" +#include "base/process/process_handle.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/platform_handle.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include +#endif + +#if defined(OS_POSIX) +#define SIMPLE_PLATFORM_HANDLE_TYPE MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR +#elif defined(OS_WIN) +#define SIMPLE_PLATFORM_HANDLE_TYPE MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE +#endif + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#define SHARED_BUFFER_PLATFORM_HANDLE_TYPE MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT +#else +#define SHARED_BUFFER_PLATFORM_HANDLE_TYPE SIMPLE_PLATFORM_HANDLE_TYPE +#endif + +uint64_t PlatformHandleValueFromPlatformFile(base::PlatformFile file) { +#if defined(OS_WIN) + return reinterpret_cast(file); +#else + return static_cast(file); +#endif +} + +base::PlatformFile PlatformFileFromPlatformHandleValue(uint64_t value) { +#if defined(OS_WIN) + return reinterpret_cast(value); +#else + return static_cast(value); +#endif +} + +namespace mojo { +namespace edk { +namespace { + +using PlatformWrapperTest = test::MojoTestBase; + +TEST_F(PlatformWrapperTest, WrapPlatformHandle) { + // Create a temporary file and write a message to it. + base::FilePath temp_file_path; + ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path)); + const std::string kMessage = "Hello, world!"; + EXPECT_EQ(base::WriteFile(temp_file_path, kMessage.data(), + static_cast(kMessage.size())), + static_cast(kMessage.size())); + + RUN_CHILD_ON_PIPE(ReadPlatformFile, h) + // Open the temporary file for reading, wrap its handle, and send it to + // the child along with the expected message to be read. + base::File file(temp_file_path, + base::File::FLAG_OPEN | base::File::FLAG_READ); + EXPECT_TRUE(file.IsValid()); + + MojoHandle wrapped_handle; + MojoPlatformHandle os_file; + os_file.struct_size = sizeof(MojoPlatformHandle); + os_file.type = SIMPLE_PLATFORM_HANDLE_TYPE; + os_file.value = + PlatformHandleValueFromPlatformFile(file.TakePlatformFile()); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWrapPlatformHandle(&os_file, &wrapped_handle)); + + WriteMessageWithHandles(h, kMessage, &wrapped_handle, 1); + END_CHILD() + + base::DeleteFile(temp_file_path, false); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReadPlatformFile, PlatformWrapperTest, h) { + // Read a message and a wrapped file handle; unwrap the handle. + MojoHandle wrapped_handle; + std::string message = ReadMessageWithHandles(h, &wrapped_handle, 1); + + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + ASSERT_EQ(MOJO_RESULT_OK, + MojoUnwrapPlatformHandle(wrapped_handle, &platform_handle)); + EXPECT_EQ(SIMPLE_PLATFORM_HANDLE_TYPE, platform_handle.type); + base::File file(PlatformFileFromPlatformHandleValue(platform_handle.value)); + + // Expect to read the same message from the file. + std::vector data(message.size()); + EXPECT_EQ(file.ReadAtCurrentPos(data.data(), static_cast(data.size())), + static_cast(data.size())); + EXPECT_TRUE(std::equal(message.begin(), message.end(), data.begin())); +} + +TEST_F(PlatformWrapperTest, WrapPlatformSharedBufferHandle) { + // Allocate a new platform shared buffer and write a message into it. + const std::string kMessage = "Hello, world!"; + base::SharedMemory buffer; + buffer.CreateAndMapAnonymous(kMessage.size()); + CHECK(buffer.memory()); + memcpy(buffer.memory(), kMessage.data(), kMessage.size()); + + RUN_CHILD_ON_PIPE(ReadPlatformSharedBuffer, h) + // Wrap the shared memory handle and send it to the child along with the + // expected message. + base::SharedMemoryHandle memory_handle = + base::SharedMemory::DuplicateHandle(buffer.handle()); + MojoPlatformHandle os_buffer; + os_buffer.struct_size = sizeof(MojoPlatformHandle); + os_buffer.type = SHARED_BUFFER_PLATFORM_HANDLE_TYPE; +#if defined(OS_MACOSX) && !defined(OS_IOS) + os_buffer.value = static_cast(memory_handle.GetMemoryObject()); +#elif defined(OS_POSIX) + os_buffer.value = static_cast(memory_handle.fd); +#elif defined(OS_WIN) + os_buffer.value = reinterpret_cast(memory_handle.GetHandle()); +#endif + + MojoHandle wrapped_handle; + ASSERT_EQ(MOJO_RESULT_OK, + MojoWrapPlatformSharedBufferHandle( + &os_buffer, kMessage.size(), + MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE, + &wrapped_handle)); + WriteMessageWithHandles(h, kMessage, &wrapped_handle, 1); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReadPlatformSharedBuffer, PlatformWrapperTest, + h) { + // Read a message and a wrapped shared buffer handle. + MojoHandle wrapped_handle; + std::string message = ReadMessageWithHandles(h, &wrapped_handle, 1); + + // Check the message in the buffer + ExpectBufferContents(wrapped_handle, 0, message); + + // Now unwrap the buffer and verify that the base::SharedMemoryHandle also + // works as expected. + MojoPlatformHandle os_buffer; + os_buffer.struct_size = sizeof(MojoPlatformHandle); + size_t size; + MojoPlatformSharedBufferHandleFlags flags; + ASSERT_EQ(MOJO_RESULT_OK, + MojoUnwrapPlatformSharedBufferHandle(wrapped_handle, &os_buffer, + &size, &flags)); + bool read_only = flags & MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE; + EXPECT_FALSE(read_only); + +#if defined(OS_MACOSX) && !defined(OS_IOS) + ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT, os_buffer.type); + base::SharedMemoryHandle memory_handle( + static_cast(os_buffer.value), size, + base::GetCurrentProcId()); +#elif defined(OS_POSIX) + ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR, os_buffer.type); + base::SharedMemoryHandle memory_handle(static_cast(os_buffer.value), + false); +#elif defined(OS_WIN) + ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE, os_buffer.type); + base::SharedMemoryHandle memory_handle( + reinterpret_cast(os_buffer.value), base::GetCurrentProcId()); +#endif + + base::SharedMemory memory(memory_handle, read_only); + memory.Map(message.size()); + ASSERT_TRUE(memory.memory()); + + EXPECT_TRUE(std::equal(message.begin(), message.end(), + static_cast(memory.memory()))); +} + +TEST_F(PlatformWrapperTest, InvalidHandle) { + // Wrap an invalid platform handle and expect to unwrap the same. + + MojoHandle wrapped_handle; + MojoPlatformHandle invalid_handle; + invalid_handle.struct_size = sizeof(MojoPlatformHandle); + invalid_handle.type = MOJO_PLATFORM_HANDLE_TYPE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWrapPlatformHandle(&invalid_handle, &wrapped_handle)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoUnwrapPlatformHandle(wrapped_handle, &invalid_handle)); + EXPECT_EQ(MOJO_PLATFORM_HANDLE_TYPE_INVALID, invalid_handle.type); +} + +TEST_F(PlatformWrapperTest, InvalidArgument) { + // Try to wrap an invalid MojoPlatformHandle struct and expect an error. + MojoHandle wrapped_handle; + MojoPlatformHandle platform_handle; + platform_handle.struct_size = 0; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWrapPlatformHandle(&platform_handle, &wrapped_handle)); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/BUILD.gn b/mojo/edk/system/ports/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..5c827619824ae055b463ba3900e2517e8591f8c6 --- /dev/null +++ b/mojo/edk/system/ports/BUILD.gn @@ -0,0 +1,46 @@ +# Copyright 2016 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("//testing/test.gni") + +source_set("ports") { + sources = [ + "event.cc", + "event.h", + "message.cc", + "message.h", + "message_filter.h", + "message_queue.cc", + "message_queue.h", + "name.cc", + "name.h", + "node.cc", + "node.h", + "node_delegate.h", + "port.cc", + "port.h", + "port_ref.cc", + "port_ref.h", + "user_data.h", + ] + + public_deps = [ + "//base", + ] +} + +source_set("tests") { + testonly = true + + sources = [ + "ports_unittest.cc", + ] + + deps = [ + ":ports", + "//base", + "//base/test:test_support", + "//testing/gtest", + ] +} diff --git a/mojo/edk/system/ports/event.cc b/mojo/edk/system/ports/event.cc new file mode 100644 index 0000000000000000000000000000000000000000..2e2208641faefd437788a4bb482740d1be811f38 --- /dev/null +++ b/mojo/edk/system/ports/event.cc @@ -0,0 +1,46 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/ports/event.h" + +#include + +namespace mojo { +namespace edk { +namespace ports { + +namespace { + +const size_t kPortsMessageAlignment = 8; + +static_assert(sizeof(PortDescriptor) % kPortsMessageAlignment == 0, + "Invalid PortDescriptor size."); + +static_assert(sizeof(EventHeader) % kPortsMessageAlignment == 0, + "Invalid EventHeader size."); + +static_assert(sizeof(UserEventData) % kPortsMessageAlignment == 0, + "Invalid UserEventData size."); + +static_assert(sizeof(ObserveProxyEventData) % kPortsMessageAlignment == 0, + "Invalid ObserveProxyEventData size."); + +static_assert(sizeof(ObserveProxyAckEventData) % kPortsMessageAlignment == 0, + "Invalid ObserveProxyAckEventData size."); + +static_assert(sizeof(ObserveClosureEventData) % kPortsMessageAlignment == 0, + "Invalid ObserveClosureEventData size."); + +static_assert(sizeof(MergePortEventData) % kPortsMessageAlignment == 0, + "Invalid MergePortEventData size."); + +} // namespace + +PortDescriptor::PortDescriptor() { + memset(padding, 0, sizeof(padding)); +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/event.h b/mojo/edk/system/ports/event.h new file mode 100644 index 0000000000000000000000000000000000000000..a66dfc118679cac6eba131efe43b5a4fc29e7ba6 --- /dev/null +++ b/mojo/edk/system/ports/event.h @@ -0,0 +1,111 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_PORTS_EVENT_H_ +#define MOJO_EDK_SYSTEM_PORTS_EVENT_H_ + +#include + +#include "mojo/edk/system/ports/message.h" +#include "mojo/edk/system/ports/name.h" + +namespace mojo { +namespace edk { +namespace ports { + +#pragma pack(push, 1) + +// TODO: Add static assertions of alignment. + +struct PortDescriptor { + PortDescriptor(); + + NodeName peer_node_name; + PortName peer_port_name; + NodeName referring_node_name; + PortName referring_port_name; + uint64_t next_sequence_num_to_send; + uint64_t next_sequence_num_to_receive; + uint64_t last_sequence_num_to_receive; + bool peer_closed; + char padding[7]; +}; + +enum struct EventType : uint32_t { + kUser, + kPortAccepted, + kObserveProxy, + kObserveProxyAck, + kObserveClosure, + kMergePort, +}; + +struct EventHeader { + EventType type; + uint32_t padding; + PortName port_name; +}; + +struct UserEventData { + uint64_t sequence_num; + uint32_t num_ports; + uint32_t padding; +}; + +struct ObserveProxyEventData { + NodeName proxy_node_name; + PortName proxy_port_name; + NodeName proxy_to_node_name; + PortName proxy_to_port_name; +}; + +struct ObserveProxyAckEventData { + uint64_t last_sequence_num; +}; + +struct ObserveClosureEventData { + uint64_t last_sequence_num; +}; + +struct MergePortEventData { + PortName new_port_name; + PortDescriptor new_port_descriptor; +}; + +#pragma pack(pop) + +inline const EventHeader* GetEventHeader(const Message& message) { + return static_cast(message.header_bytes()); +} + +inline EventHeader* GetMutableEventHeader(Message* message) { + return static_cast(message->mutable_header_bytes()); +} + +template +inline const EventData* GetEventData(const Message& message) { + return reinterpret_cast( + reinterpret_cast(GetEventHeader(message) + 1)); +} + +template +inline EventData* GetMutableEventData(Message* message) { + return reinterpret_cast( + reinterpret_cast(GetMutableEventHeader(message) + 1)); +} + +inline const PortDescriptor* GetPortDescriptors(const UserEventData* event) { + return reinterpret_cast( + reinterpret_cast(event + 1)); +} + +inline PortDescriptor* GetMutablePortDescriptors(UserEventData* event) { + return reinterpret_cast(reinterpret_cast(event + 1)); +} + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_EVENT_H_ diff --git a/mojo/edk/system/ports/message.cc b/mojo/edk/system/ports/message.cc new file mode 100644 index 0000000000000000000000000000000000000000..5d3c000a3a4a7f294d1d70472fc00032ae44cdeb --- /dev/null +++ b/mojo/edk/system/ports/message.cc @@ -0,0 +1,100 @@ +// Copyright 2016 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. + +#include + +#include + +#include "base/logging.h" +#include "mojo/edk/system/ports/event.h" + +namespace mojo { +namespace edk { +namespace ports { + +// static +bool Message::Parse(const void* bytes, + size_t num_bytes, + size_t* num_header_bytes, + size_t* num_payload_bytes, + size_t* num_ports_bytes) { + if (num_bytes < sizeof(EventHeader)) + return false; + const EventHeader* header = static_cast(bytes); + switch (header->type) { + case EventType::kUser: + // See below. + break; + case EventType::kPortAccepted: + *num_header_bytes = sizeof(EventHeader); + break; + case EventType::kObserveProxy: + *num_header_bytes = sizeof(EventHeader) + sizeof(ObserveProxyEventData); + break; + case EventType::kObserveProxyAck: + *num_header_bytes = + sizeof(EventHeader) + sizeof(ObserveProxyAckEventData); + break; + case EventType::kObserveClosure: + *num_header_bytes = sizeof(EventHeader) + sizeof(ObserveClosureEventData); + break; + case EventType::kMergePort: + *num_header_bytes = sizeof(EventHeader) + sizeof(MergePortEventData); + break; + default: + return false; + } + + if (header->type == EventType::kUser) { + if (num_bytes < sizeof(EventHeader) + sizeof(UserEventData)) + return false; + const UserEventData* event_data = + reinterpret_cast( + reinterpret_cast(header + 1)); + if (event_data->num_ports > std::numeric_limits::max()) + return false; + *num_header_bytes = sizeof(EventHeader) + + sizeof(UserEventData) + + event_data->num_ports * sizeof(PortDescriptor); + *num_ports_bytes = event_data->num_ports * sizeof(PortName); + if (num_bytes < *num_header_bytes + *num_ports_bytes) + return false; + *num_payload_bytes = num_bytes - *num_header_bytes - *num_ports_bytes; + } else { + if (*num_header_bytes != num_bytes) + return false; + *num_payload_bytes = 0; + *num_ports_bytes = 0; + } + + return true; +} + +Message::Message(size_t num_payload_bytes, size_t num_ports) + : Message(sizeof(EventHeader) + sizeof(UserEventData) + + num_ports * sizeof(PortDescriptor), + num_payload_bytes, num_ports * sizeof(PortName)) { + num_ports_ = num_ports; +} + +Message::Message(size_t num_header_bytes, + size_t num_payload_bytes, + size_t num_ports_bytes) + : start_(nullptr), + num_header_bytes_(num_header_bytes), + num_ports_bytes_(num_ports_bytes), + num_payload_bytes_(num_payload_bytes) { +} + +void Message::InitializeUserMessageHeader(void* start) { + start_ = static_cast(start); + memset(start_, 0, num_header_bytes_); + GetMutableEventHeader(this)->type = EventType::kUser; + GetMutableEventData(this)->num_ports = + static_cast(num_ports_); +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/message.h b/mojo/edk/system/ports/message.h new file mode 100644 index 0000000000000000000000000000000000000000..95fa04676ce0e670025a74036e6f24fed4683ad1 --- /dev/null +++ b/mojo/edk/system/ports/message.h @@ -0,0 +1,93 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_PORTS_MESSAGE_H_ +#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_H_ + +#include + +#include + +#include "mojo/edk/system/ports/name.h" + +namespace mojo { +namespace edk { +namespace ports { + +// A message consists of a header (array of bytes), payload (array of bytes) +// and an array of ports. The header is used by the Node implementation. +// +// This class is designed to be subclassed, and the subclass is responsible for +// providing the underlying storage. The header size will be aligned, and it +// should be followed in memory by the array of ports and finally the payload. +// +// NOTE: This class does not manage the lifetime of the ports it references. +class Message { + public: + virtual ~Message() {} + + // Inspect the message at |bytes| and return the size of each section. Returns + // |false| if the message is malformed and |true| otherwise. + static bool Parse(const void* bytes, + size_t num_bytes, + size_t* num_header_bytes, + size_t* num_payload_bytes, + size_t* num_ports_bytes); + + void* mutable_header_bytes() { return start_; } + const void* header_bytes() const { return start_; } + size_t num_header_bytes() const { return num_header_bytes_; } + + void* mutable_payload_bytes() { + return start_ + num_header_bytes_ + num_ports_bytes_; + } + const void* payload_bytes() const { + return const_cast(this)->mutable_payload_bytes(); + } + size_t num_payload_bytes() const { return num_payload_bytes_; } + + PortName* mutable_ports() { + return reinterpret_cast(start_ + num_header_bytes_); + } + const PortName* ports() const { + return const_cast(this)->mutable_ports(); + } + size_t num_ports_bytes() const { return num_ports_bytes_; } + size_t num_ports() const { return num_ports_bytes_ / sizeof(PortName); } + + protected: + // Constructs a new Message base for a user message. + // + // Note: You MUST call InitializeUserMessageHeader() before this Message is + // ready for transmission. + Message(size_t num_payload_bytes, size_t num_ports); + + // Constructs a new Message base for an internal message. Do NOT call + // InitializeUserMessageHeader() when using this constructor. + Message(size_t num_header_bytes, + size_t num_payload_bytes, + size_t num_ports_bytes); + + Message(const Message& other) = delete; + void operator=(const Message& other) = delete; + + // Initializes the header in a newly allocated message buffer to carry a + // user message. + void InitializeUserMessageHeader(void* start); + + // Note: storage is [header][ports][payload]. + char* start_ = nullptr; + size_t num_ports_ = 0; + size_t num_header_bytes_ = 0; + size_t num_ports_bytes_ = 0; + size_t num_payload_bytes_ = 0; +}; + +using ScopedMessage = std::unique_ptr; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_MESSAGE_H_ diff --git a/mojo/edk/system/ports/message_filter.h b/mojo/edk/system/ports/message_filter.h new file mode 100644 index 0000000000000000000000000000000000000000..bf8fa2196659312212666dda105b85171ab5a480 --- /dev/null +++ b/mojo/edk/system/ports/message_filter.h @@ -0,0 +1,29 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_PORTS_MESSAGE_FILTER_H_ +#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_FILTER_H_ + +namespace mojo { +namespace edk { +namespace ports { + +class Message; + +// An interface which can be implemented to filter port messages according to +// arbitrary policy. +class MessageFilter { + public: + virtual ~MessageFilter() {} + + // Returns true of |message| should be accepted by whomever is applying this + // filter. See MessageQueue::GetNextMessage(), for example. + virtual bool Match(const Message& message) = 0; +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_MESSAGE_FILTER_H_ diff --git a/mojo/edk/system/ports/message_queue.cc b/mojo/edk/system/ports/message_queue.cc new file mode 100644 index 0000000000000000000000000000000000000000..defb1b6c759670a963f5566c11f5babffc75fec0 --- /dev/null +++ b/mojo/edk/system/ports/message_queue.cc @@ -0,0 +1,87 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/ports/message_queue.h" + +#include + +#include "base/logging.h" +#include "mojo/edk/system/ports/event.h" +#include "mojo/edk/system/ports/message_filter.h" + +namespace mojo { +namespace edk { +namespace ports { + +inline uint64_t GetSequenceNum(const ScopedMessage& message) { + return GetEventData(*message)->sequence_num; +} + +// Used by std::{push,pop}_heap functions +inline bool operator<(const ScopedMessage& a, const ScopedMessage& b) { + return GetSequenceNum(a) > GetSequenceNum(b); +} + +MessageQueue::MessageQueue() : MessageQueue(kInitialSequenceNum) {} + +MessageQueue::MessageQueue(uint64_t next_sequence_num) + : next_sequence_num_(next_sequence_num) { + // The message queue is blocked waiting for a message with sequence number + // equal to |next_sequence_num|. +} + +MessageQueue::~MessageQueue() { +#if DCHECK_IS_ON() + size_t num_leaked_ports = 0; + for (const auto& message : heap_) + num_leaked_ports += message->num_ports(); + DVLOG_IF(1, num_leaked_ports > 0) + << "Leaking " << num_leaked_ports << " ports in unreceived messages"; +#endif +} + +bool MessageQueue::HasNextMessage() const { + return !heap_.empty() && GetSequenceNum(heap_[0]) == next_sequence_num_; +} + +void MessageQueue::GetNextMessage(ScopedMessage* message, + MessageFilter* filter) { + if (!HasNextMessage() || (filter && !filter->Match(*heap_[0].get()))) { + message->reset(); + return; + } + + std::pop_heap(heap_.begin(), heap_.end()); + *message = std::move(heap_.back()); + heap_.pop_back(); + + next_sequence_num_++; +} + +void MessageQueue::AcceptMessage(ScopedMessage message, + bool* has_next_message) { + DCHECK(GetEventHeader(*message)->type == EventType::kUser); + + // TODO: Handle sequence number roll-over. + + heap_.emplace_back(std::move(message)); + std::push_heap(heap_.begin(), heap_.end()); + + if (!signalable_) { + *has_next_message = false; + } else { + *has_next_message = (GetSequenceNum(heap_[0]) == next_sequence_num_); + } +} + +void MessageQueue::GetReferencedPorts(std::deque* port_names) { + for (const auto& message : heap_) { + for (size_t i = 0; i < message->num_ports(); ++i) + port_names->push_back(message->ports()[i]); + } +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/message_queue.h b/mojo/edk/system/ports/message_queue.h new file mode 100644 index 0000000000000000000000000000000000000000..d9a47ed0a138dbda6e3b61bce5215414164cef26 --- /dev/null +++ b/mojo/edk/system/ports/message_queue.h @@ -0,0 +1,73 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_PORTS_MESSAGE_QUEUE_H_ +#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_QUEUE_H_ + +#include + +#include +#include +#include +#include + +#include "base/macros.h" +#include "mojo/edk/system/ports/message.h" + +namespace mojo { +namespace edk { +namespace ports { + +const uint64_t kInitialSequenceNum = 1; +const uint64_t kInvalidSequenceNum = std::numeric_limits::max(); + +class MessageFilter; + +// An incoming message queue for a port. MessageQueue keeps track of the highest +// known sequence number and can indicate whether the next sequential message is +// available. Thus the queue enforces message ordering for the consumer without +// enforcing it for the producer (see AcceptMessage() below.) +class MessageQueue { + public: + explicit MessageQueue(); + explicit MessageQueue(uint64_t next_sequence_num); + ~MessageQueue(); + + void set_signalable(bool value) { signalable_ = value; } + + uint64_t next_sequence_num() const { return next_sequence_num_; } + + bool HasNextMessage() const; + + // Gives ownership of the message. If |filter| is non-null, the next message + // will only be retrieved if the filter successfully matches it. + void GetNextMessage(ScopedMessage* message, MessageFilter* filter); + + // Takes ownership of the message. Note: Messages are ordered, so while we + // have added a message to the queue, we may still be waiting on a message + // ahead of this one before we can let any of the messages be returned by + // GetNextMessage. + // + // Furthermore, once has_next_message is set to true, it will remain false + // until GetNextMessage is called enough times to return a null message. + // In other words, has_next_message acts like an edge trigger. + // + void AcceptMessage(ScopedMessage message, bool* has_next_message); + + // Returns all of the ports referenced by messages in this message queue. + void GetReferencedPorts(std::deque* ports); + + private: + std::vector heap_; + uint64_t next_sequence_num_; + bool signalable_ = true; + + DISALLOW_COPY_AND_ASSIGN(MessageQueue); +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_MESSAGE_QUEUE_H_ diff --git a/mojo/edk/system/ports/name.cc b/mojo/edk/system/ports/name.cc new file mode 100644 index 0000000000000000000000000000000000000000..ea17698f978c49eb60ae7833b7da57c1f6cfe9b7 --- /dev/null +++ b/mojo/edk/system/ports/name.cc @@ -0,0 +1,26 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/ports/name.h" + +namespace mojo { +namespace edk { +namespace ports { + +extern const PortName kInvalidPortName = {0, 0}; + +extern const NodeName kInvalidNodeName = {0, 0}; + +std::ostream& operator<<(std::ostream& stream, const Name& name) { + std::ios::fmtflags flags(stream.flags()); + stream << std::hex << std::uppercase << name.v1; + if (name.v2 != 0) + stream << '.' << name.v2; + stream.flags(flags); + return stream; +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/name.h b/mojo/edk/system/ports/name.h new file mode 100644 index 0000000000000000000000000000000000000000..72e41b92ab68a2ddad163848da7719c2e4ce4522 --- /dev/null +++ b/mojo/edk/system/ports/name.h @@ -0,0 +1,74 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_PORTS_NAME_H_ +#define MOJO_EDK_SYSTEM_PORTS_NAME_H_ + +#include + +#include +#include + +#include "base/hash.h" + +namespace mojo { +namespace edk { +namespace ports { + +struct Name { + Name(uint64_t v1, uint64_t v2) : v1(v1), v2(v2) {} + uint64_t v1, v2; +}; + +inline bool operator==(const Name& a, const Name& b) { + return a.v1 == b.v1 && a.v2 == b.v2; +} + +inline bool operator!=(const Name& a, const Name& b) { + return !(a == b); +} + +inline bool operator<(const Name& a, const Name& b) { + return std::tie(a.v1, a.v2) < std::tie(b.v1, b.v2); +} + +std::ostream& operator<<(std::ostream& stream, const Name& name); + +struct PortName : Name { + PortName() : Name(0, 0) {} + PortName(uint64_t v1, uint64_t v2) : Name(v1, v2) {} +}; + +extern const PortName kInvalidPortName; + +struct NodeName : Name { + NodeName() : Name(0, 0) {} + NodeName(uint64_t v1, uint64_t v2) : Name(v1, v2) {} +}; + +extern const NodeName kInvalidNodeName; + +} // namespace ports +} // namespace edk +} // namespace mojo + +namespace std { + +template <> +struct hash { + std::size_t operator()(const mojo::edk::ports::PortName& name) const { + return base::HashInts64(name.v1, name.v2); + } +}; + +template <> +struct hash { + std::size_t operator()(const mojo::edk::ports::NodeName& name) const { + return base::HashInts64(name.v1, name.v2); + } +}; + +} // namespace std + +#endif // MOJO_EDK_SYSTEM_PORTS_NAME_H_ diff --git a/mojo/edk/system/ports/node.cc b/mojo/edk/system/ports/node.cc new file mode 100644 index 0000000000000000000000000000000000000000..f9a3feb8058d3e5b22575ea620454d2905709e8c --- /dev/null +++ b/mojo/edk/system/ports/node.cc @@ -0,0 +1,1385 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/ports/node.h" + +#include + +#include + +#include "base/atomicops.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/ports/node_delegate.h" + +namespace mojo { +namespace edk { +namespace ports { + +namespace { + +int DebugError(const char* message, int error_code) { + CHECK(false) << "Oops: " << message; + return error_code; +} + +#define OOPS(x) DebugError(#x, x) + +bool CanAcceptMoreMessages(const Port* port) { + // Have we already doled out the last message (i.e., do we expect to NOT + // receive further messages)? + uint64_t next_sequence_num = port->message_queue.next_sequence_num(); + if (port->state == Port::kClosed) + return false; + if (port->peer_closed || port->remove_proxy_on_last_message) { + if (port->last_sequence_num_to_receive == next_sequence_num - 1) + return false; + } + return true; +} + +} // namespace + +class Node::LockedPort { + public: + explicit LockedPort(Port* port) : port_(port) { + port_->lock.AssertAcquired(); + } + + Port* get() const { return port_; } + Port* operator->() const { return port_; } + + private: + Port* const port_; +}; + +Node::Node(const NodeName& name, NodeDelegate* delegate) + : name_(name), + delegate_(delegate) { +} + +Node::~Node() { + if (!ports_.empty()) + DLOG(WARNING) << "Unclean shutdown for node " << name_; +} + +bool Node::CanShutdownCleanly(ShutdownPolicy policy) { + base::AutoLock ports_lock(ports_lock_); + + if (policy == ShutdownPolicy::DONT_ALLOW_LOCAL_PORTS) { +#if DCHECK_IS_ON() + for (auto entry : ports_) { + DVLOG(2) << "Port " << entry.first << " referencing node " + << entry.second->peer_node_name << " is blocking shutdown of " + << "node " << name_ << " (state=" << entry.second->state << ")"; + } +#endif + return ports_.empty(); + } + + DCHECK_EQ(policy, ShutdownPolicy::ALLOW_LOCAL_PORTS); + + // NOTE: This is not efficient, though it probably doesn't need to be since + // relatively few ports should be open during shutdown and shutdown doesn't + // need to be blazingly fast. + bool can_shutdown = true; + for (auto entry : ports_) { + base::AutoLock lock(entry.second->lock); + if (entry.second->peer_node_name != name_ && + entry.second->state != Port::kReceiving) { + can_shutdown = false; +#if DCHECK_IS_ON() + DVLOG(2) << "Port " << entry.first << " referencing node " + << entry.second->peer_node_name << " is blocking shutdown of " + << "node " << name_ << " (state=" << entry.second->state << ")"; +#else + // Exit early when not debugging. + break; +#endif + } + } + + return can_shutdown; +} + +int Node::GetPort(const PortName& port_name, PortRef* port_ref) { + scoped_refptr port = GetPort(port_name); + if (!port) + return ERROR_PORT_UNKNOWN; + + *port_ref = PortRef(port_name, std::move(port)); + return OK; +} + +int Node::CreateUninitializedPort(PortRef* port_ref) { + PortName port_name; + delegate_->GenerateRandomPortName(&port_name); + + scoped_refptr port(new Port(kInitialSequenceNum, kInitialSequenceNum)); + int rv = AddPortWithName(port_name, port); + if (rv != OK) + return rv; + + *port_ref = PortRef(port_name, std::move(port)); + return OK; +} + +int Node::InitializePort(const PortRef& port_ref, + const NodeName& peer_node_name, + const PortName& peer_port_name) { + Port* port = port_ref.port(); + + { + base::AutoLock lock(port->lock); + if (port->state != Port::kUninitialized) + return ERROR_PORT_STATE_UNEXPECTED; + + port->state = Port::kReceiving; + port->peer_node_name = peer_node_name; + port->peer_port_name = peer_port_name; + } + + delegate_->PortStatusChanged(port_ref); + + return OK; +} + +int Node::CreatePortPair(PortRef* port0_ref, PortRef* port1_ref) { + int rv; + + rv = CreateUninitializedPort(port0_ref); + if (rv != OK) + return rv; + + rv = CreateUninitializedPort(port1_ref); + if (rv != OK) + return rv; + + rv = InitializePort(*port0_ref, name_, port1_ref->name()); + if (rv != OK) + return rv; + + rv = InitializePort(*port1_ref, name_, port0_ref->name()); + if (rv != OK) + return rv; + + return OK; +} + +int Node::SetUserData(const PortRef& port_ref, + scoped_refptr user_data) { + Port* port = port_ref.port(); + + base::AutoLock lock(port->lock); + if (port->state == Port::kClosed) + return ERROR_PORT_STATE_UNEXPECTED; + + port->user_data = std::move(user_data); + + return OK; +} + +int Node::GetUserData(const PortRef& port_ref, + scoped_refptr* user_data) { + Port* port = port_ref.port(); + + base::AutoLock lock(port->lock); + if (port->state == Port::kClosed) + return ERROR_PORT_STATE_UNEXPECTED; + + *user_data = port->user_data; + + return OK; +} + +int Node::ClosePort(const PortRef& port_ref) { + std::deque referenced_port_names; + + ObserveClosureEventData data; + + NodeName peer_node_name; + PortName peer_port_name; + Port* port = port_ref.port(); + { + // We may need to erase the port, which requires ports_lock_ to be held, + // but ports_lock_ must be acquired before any individual port locks. + base::AutoLock ports_lock(ports_lock_); + + base::AutoLock lock(port->lock); + if (port->state == Port::kUninitialized) { + // If the port was not yet initialized, there's nothing interesting to do. + ErasePort_Locked(port_ref.name()); + return OK; + } + + if (port->state != Port::kReceiving) + return ERROR_PORT_STATE_UNEXPECTED; + + port->state = Port::kClosed; + + // We pass along the sequence number of the last message sent from this + // port to allow the peer to have the opportunity to consume all inbound + // messages before notifying the embedder that this port is closed. + data.last_sequence_num = port->next_sequence_num_to_send - 1; + + peer_node_name = port->peer_node_name; + peer_port_name = port->peer_port_name; + + // If the port being closed still has unread messages, then we need to take + // care to close those ports so as to avoid leaking memory. + port->message_queue.GetReferencedPorts(&referenced_port_names); + + ErasePort_Locked(port_ref.name()); + } + + DVLOG(2) << "Sending ObserveClosure from " << port_ref.name() << "@" << name_ + << " to " << peer_port_name << "@" << peer_node_name; + + delegate_->ForwardMessage( + peer_node_name, + NewInternalMessage(peer_port_name, EventType::kObserveClosure, data)); + + for (const auto& name : referenced_port_names) { + PortRef ref; + if (GetPort(name, &ref) == OK) + ClosePort(ref); + } + return OK; +} + +int Node::GetStatus(const PortRef& port_ref, PortStatus* port_status) { + Port* port = port_ref.port(); + + base::AutoLock lock(port->lock); + + if (port->state != Port::kReceiving) + return ERROR_PORT_STATE_UNEXPECTED; + + port_status->has_messages = port->message_queue.HasNextMessage(); + port_status->receiving_messages = CanAcceptMoreMessages(port); + port_status->peer_closed = port->peer_closed; + return OK; +} + +int Node::GetMessage(const PortRef& port_ref, + ScopedMessage* message, + MessageFilter* filter) { + *message = nullptr; + + DVLOG(4) << "GetMessage for " << port_ref.name() << "@" << name_; + + Port* port = port_ref.port(); + { + base::AutoLock lock(port->lock); + + // This could also be treated like the port being unknown since the + // embedder should no longer be referring to a port that has been sent. + if (port->state != Port::kReceiving) + return ERROR_PORT_STATE_UNEXPECTED; + + // Let the embedder get messages until there are no more before reporting + // that the peer closed its end. + if (!CanAcceptMoreMessages(port)) + return ERROR_PORT_PEER_CLOSED; + + port->message_queue.GetNextMessage(message, filter); + } + + // Allow referenced ports to trigger PortStatusChanged calls. + if (*message) { + for (size_t i = 0; i < (*message)->num_ports(); ++i) { + const PortName& new_port_name = (*message)->ports()[i]; + scoped_refptr new_port = GetPort(new_port_name); + + DCHECK(new_port) << "Port " << new_port_name << "@" << name_ + << " does not exist!"; + + base::AutoLock lock(new_port->lock); + + DCHECK(new_port->state == Port::kReceiving); + new_port->message_queue.set_signalable(true); + } + } + + return OK; +} + +int Node::SendMessage(const PortRef& port_ref, ScopedMessage message) { + int rv = SendMessageInternal(port_ref, &message); + if (rv != OK) { + // If send failed, close all carried ports. Note that we're careful not to + // close the sending port itself if it happened to be one of the encoded + // ports (an invalid but possible condition.) + for (size_t i = 0; i < message->num_ports(); ++i) { + if (message->ports()[i] == port_ref.name()) + continue; + + PortRef port; + if (GetPort(message->ports()[i], &port) == OK) + ClosePort(port); + } + } + return rv; +} + +int Node::AcceptMessage(ScopedMessage message) { + const EventHeader* header = GetEventHeader(*message); + switch (header->type) { + case EventType::kUser: + return OnUserMessage(std::move(message)); + case EventType::kPortAccepted: + return OnPortAccepted(header->port_name); + case EventType::kObserveProxy: + return OnObserveProxy( + header->port_name, + *GetEventData(*message)); + case EventType::kObserveProxyAck: + return OnObserveProxyAck( + header->port_name, + GetEventData(*message)->last_sequence_num); + case EventType::kObserveClosure: + return OnObserveClosure( + header->port_name, + GetEventData(*message)->last_sequence_num); + case EventType::kMergePort: + return OnMergePort(header->port_name, + *GetEventData(*message)); + } + return OOPS(ERROR_NOT_IMPLEMENTED); +} + +int Node::MergePorts(const PortRef& port_ref, + const NodeName& destination_node_name, + const PortName& destination_port_name) { + Port* port = port_ref.port(); + MergePortEventData data; + { + base::AutoLock lock(port->lock); + + DVLOG(1) << "Sending MergePort from " << port_ref.name() << "@" << name_ + << " to " << destination_port_name << "@" << destination_node_name; + + // Send the port-to-merge over to the destination node so it can be merged + // into the port cycle atomically there. + data.new_port_name = port_ref.name(); + WillSendPort(LockedPort(port), destination_node_name, &data.new_port_name, + &data.new_port_descriptor); + } + delegate_->ForwardMessage( + destination_node_name, + NewInternalMessage(destination_port_name, + EventType::kMergePort, data)); + return OK; +} + +int Node::MergeLocalPorts(const PortRef& port0_ref, const PortRef& port1_ref) { + Port* port0 = port0_ref.port(); + Port* port1 = port1_ref.port(); + int rv; + { + // |ports_lock_| must be held when acquiring overlapping port locks. + base::AutoLock ports_lock(ports_lock_); + base::AutoLock port0_lock(port0->lock); + base::AutoLock port1_lock(port1->lock); + + DVLOG(1) << "Merging local ports " << port0_ref.name() << "@" << name_ + << " and " << port1_ref.name() << "@" << name_; + + if (port0->state != Port::kReceiving || port1->state != Port::kReceiving) + rv = ERROR_PORT_STATE_UNEXPECTED; + else + rv = MergePorts_Locked(port0_ref, port1_ref); + } + + if (rv != OK) { + ClosePort(port0_ref); + ClosePort(port1_ref); + } + + return rv; +} + +int Node::LostConnectionToNode(const NodeName& node_name) { + // We can no longer send events to the given node. We also can't expect any + // PortAccepted events. + + DVLOG(1) << "Observing lost connection from node " << name_ + << " to node " << node_name; + + DestroyAllPortsWithPeer(node_name, kInvalidPortName); + return OK; +} + +int Node::OnUserMessage(ScopedMessage message) { + PortName port_name = GetEventHeader(*message)->port_name; + const auto* event = GetEventData(*message); + +#if DCHECK_IS_ON() + std::ostringstream ports_buf; + for (size_t i = 0; i < message->num_ports(); ++i) { + if (i > 0) + ports_buf << ","; + ports_buf << message->ports()[i]; + } + + DVLOG(4) << "AcceptMessage " << event->sequence_num + << " [ports=" << ports_buf.str() << "] at " + << port_name << "@" << name_; +#endif + + scoped_refptr port = GetPort(port_name); + + // Even if this port does not exist, cannot receive anymore messages or is + // buffering or proxying messages, we still need these ports to be bound to + // this node. When the message is forwarded, these ports will get transferred + // following the usual method. If the message cannot be accepted, then the + // newly bound ports will simply be closed. + + for (size_t i = 0; i < message->num_ports(); ++i) { + int rv = AcceptPort(message->ports()[i], GetPortDescriptors(event)[i]); + if (rv != OK) + return rv; + } + + bool has_next_message = false; + bool message_accepted = false; + + if (port) { + // We may want to forward messages once the port lock is held, so we must + // acquire |ports_lock_| first. + base::AutoLock ports_lock(ports_lock_); + base::AutoLock lock(port->lock); + + // Reject spurious messages if we've already received the last expected + // message. + if (CanAcceptMoreMessages(port.get())) { + message_accepted = true; + port->message_queue.AcceptMessage(std::move(message), &has_next_message); + + if (port->state == Port::kBuffering) { + has_next_message = false; + } else if (port->state == Port::kProxying) { + has_next_message = false; + + // Forward messages. We forward messages in sequential order here so + // that we maintain the message queue's notion of next sequence number. + // That's useful for the proxy removal process as we can tell when this + // port has seen all of the messages it is expected to see. + int rv = ForwardMessages_Locked(LockedPort(port.get()), port_name); + if (rv != OK) + return rv; + + MaybeRemoveProxy_Locked(LockedPort(port.get()), port_name); + } + } + } + + if (!message_accepted) { + DVLOG(2) << "Message not accepted!\n"; + // Close all newly accepted ports as they are effectively orphaned. + for (size_t i = 0; i < message->num_ports(); ++i) { + PortRef port_ref; + if (GetPort(message->ports()[i], &port_ref) == OK) { + ClosePort(port_ref); + } else { + DLOG(WARNING) << "Cannot close non-existent port!\n"; + } + } + } else if (has_next_message) { + PortRef port_ref(port_name, port); + delegate_->PortStatusChanged(port_ref); + } + + return OK; +} + +int Node::OnPortAccepted(const PortName& port_name) { + scoped_refptr port = GetPort(port_name); + if (!port) + return ERROR_PORT_UNKNOWN; + + DVLOG(2) << "PortAccepted at " << port_name << "@" << name_ + << " pointing to " + << port->peer_port_name << "@" << port->peer_node_name; + + return BeginProxying(PortRef(port_name, std::move(port))); +} + +int Node::OnObserveProxy(const PortName& port_name, + const ObserveProxyEventData& event) { + if (port_name == kInvalidPortName) { + // An ObserveProxy with an invalid target port name is a broadcast used to + // inform ports when their peer (which was itself a proxy) has become + // defunct due to unexpected node disconnection. + // + // Receiving ports affected by this treat it as equivalent to peer closure. + // Proxies affected by this can be removed and will in turn broadcast their + // own death with a similar message. + CHECK_EQ(event.proxy_to_node_name, kInvalidNodeName); + CHECK_EQ(event.proxy_to_port_name, kInvalidPortName); + DestroyAllPortsWithPeer(event.proxy_node_name, event.proxy_port_name); + return OK; + } + + // The port may have already been closed locally, in which case the + // ObserveClosure message will contain the last_sequence_num field. + // We can then silently ignore this message. + scoped_refptr port = GetPort(port_name); + if (!port) { + DVLOG(1) << "ObserveProxy: " << port_name << "@" << name_ << " not found"; + return OK; + } + + DVLOG(2) << "ObserveProxy at " << port_name << "@" << name_ << ", proxy at " + << event.proxy_port_name << "@" + << event.proxy_node_name << " pointing to " + << event.proxy_to_port_name << "@" + << event.proxy_to_node_name; + + { + base::AutoLock lock(port->lock); + + if (port->peer_node_name == event.proxy_node_name && + port->peer_port_name == event.proxy_port_name) { + if (port->state == Port::kReceiving) { + port->peer_node_name = event.proxy_to_node_name; + port->peer_port_name = event.proxy_to_port_name; + + ObserveProxyAckEventData ack; + ack.last_sequence_num = port->next_sequence_num_to_send - 1; + + delegate_->ForwardMessage( + event.proxy_node_name, + NewInternalMessage(event.proxy_port_name, + EventType::kObserveProxyAck, + ack)); + } else { + // As a proxy ourselves, we don't know how to honor the ObserveProxy + // event or to populate the last_sequence_num field of ObserveProxyAck. + // Afterall, another port could be sending messages to our peer now + // that we've sent out our own ObserveProxy event. Instead, we will + // send an ObserveProxyAck indicating that the ObserveProxy event + // should be re-sent (last_sequence_num set to kInvalidSequenceNum). + // However, this has to be done after we are removed as a proxy. + // Otherwise, we might just find ourselves back here again, which + // would be akin to a busy loop. + + DVLOG(2) << "Delaying ObserveProxyAck to " + << event.proxy_port_name << "@" << event.proxy_node_name; + + ObserveProxyAckEventData ack; + ack.last_sequence_num = kInvalidSequenceNum; + + port->send_on_proxy_removal.reset( + new std::pair( + event.proxy_node_name, + NewInternalMessage(event.proxy_port_name, + EventType::kObserveProxyAck, + ack))); + } + } else { + // Forward this event along to our peer. Eventually, it should find the + // port referring to the proxy. + delegate_->ForwardMessage( + port->peer_node_name, + NewInternalMessage(port->peer_port_name, + EventType::kObserveProxy, + event)); + } + } + return OK; +} + +int Node::OnObserveProxyAck(const PortName& port_name, + uint64_t last_sequence_num) { + DVLOG(2) << "ObserveProxyAck at " << port_name << "@" << name_ + << " (last_sequence_num=" << last_sequence_num << ")"; + + scoped_refptr port = GetPort(port_name); + if (!port) + return ERROR_PORT_UNKNOWN; // The port may have observed closure first, so + // this is not an "Oops". + + { + base::AutoLock lock(port->lock); + + if (port->state != Port::kProxying) + return OOPS(ERROR_PORT_STATE_UNEXPECTED); + + if (last_sequence_num == kInvalidSequenceNum) { + // Send again. + InitiateProxyRemoval(LockedPort(port.get()), port_name); + return OK; + } + + // We can now remove this port once we have received and forwarded the last + // message addressed to this port. + port->remove_proxy_on_last_message = true; + port->last_sequence_num_to_receive = last_sequence_num; + } + TryRemoveProxy(PortRef(port_name, std::move(port))); + return OK; +} + +int Node::OnObserveClosure(const PortName& port_name, + uint64_t last_sequence_num) { + // OK if the port doesn't exist, as it may have been closed already. + scoped_refptr port = GetPort(port_name); + if (!port) + return OK; + + // This message tells the port that it should no longer expect more messages + // beyond last_sequence_num. This message is forwarded along until we reach + // the receiving end, and this message serves as an equivalent to + // ObserveProxyAck. + + bool notify_delegate = false; + ObserveClosureEventData forwarded_data; + NodeName peer_node_name; + PortName peer_port_name; + bool try_remove_proxy = false; + { + base::AutoLock lock(port->lock); + + port->peer_closed = true; + port->last_sequence_num_to_receive = last_sequence_num; + + DVLOG(2) << "ObserveClosure at " << port_name << "@" << name_ + << " (state=" << port->state << ") pointing to " + << port->peer_port_name << "@" << port->peer_node_name + << " (last_sequence_num=" << last_sequence_num << ")"; + + // We always forward ObserveClosure, even beyond the receiving port which + // cares about it. This ensures that any dead-end proxies beyond that port + // are notified to remove themselves. + + if (port->state == Port::kReceiving) { + notify_delegate = true; + + // When forwarding along the other half of the port cycle, this will only + // reach dead-end proxies. Tell them we've sent our last message so they + // can go away. + // + // TODO: Repurposing ObserveClosure for this has the desired result but + // may be semantically confusing since the forwarding port is not actually + // closed. Consider replacing this with a new event type. + forwarded_data.last_sequence_num = port->next_sequence_num_to_send - 1; + } else { + // We haven't yet reached the receiving peer of the closed port, so + // forward the message along as-is. + forwarded_data.last_sequence_num = last_sequence_num; + + // See about removing the port if it is a proxy as our peer won't be able + // to participate in proxy removal. + port->remove_proxy_on_last_message = true; + if (port->state == Port::kProxying) + try_remove_proxy = true; + } + + DVLOG(2) << "Forwarding ObserveClosure from " + << port_name << "@" << name_ << " to peer " + << port->peer_port_name << "@" << port->peer_node_name + << " (last_sequence_num=" << forwarded_data.last_sequence_num + << ")"; + + peer_node_name = port->peer_node_name; + peer_port_name = port->peer_port_name; + } + if (try_remove_proxy) + TryRemoveProxy(PortRef(port_name, port)); + + delegate_->ForwardMessage( + peer_node_name, + NewInternalMessage(peer_port_name, EventType::kObserveClosure, + forwarded_data)); + + if (notify_delegate) { + PortRef port_ref(port_name, std::move(port)); + delegate_->PortStatusChanged(port_ref); + } + return OK; +} + +int Node::OnMergePort(const PortName& port_name, + const MergePortEventData& event) { + scoped_refptr port = GetPort(port_name); + + DVLOG(1) << "MergePort at " << port_name << "@" << name_ << " (state=" + << (port ? port->state : -1) << ") merging with proxy " + << event.new_port_name + << "@" << name_ << " pointing to " + << event.new_port_descriptor.peer_port_name << "@" + << event.new_port_descriptor.peer_node_name << " referred by " + << event.new_port_descriptor.referring_port_name << "@" + << event.new_port_descriptor.referring_node_name; + + bool close_target_port = false; + bool close_new_port = false; + + // Accept the new port. This is now the receiving end of the other port cycle + // to be merged with ours. + int rv = AcceptPort(event.new_port_name, event.new_port_descriptor); + if (rv != OK) { + close_target_port = true; + } else if (port) { + // BeginProxying_Locked may call MaybeRemoveProxy_Locked, which in turn + // needs to hold |ports_lock_|. We also acquire multiple port locks within. + base::AutoLock ports_lock(ports_lock_); + base::AutoLock lock(port->lock); + + if (port->state != Port::kReceiving) { + close_new_port = true; + } else { + scoped_refptr new_port = GetPort_Locked(event.new_port_name); + base::AutoLock new_port_lock(new_port->lock); + DCHECK(new_port->state == Port::kReceiving); + + // Both ports are locked. Now all we have to do is swap their peer + // information and set them up as proxies. + + PortRef port0_ref(port_name, port); + PortRef port1_ref(event.new_port_name, new_port); + int rv = MergePorts_Locked(port0_ref, port1_ref); + if (rv == OK) + return rv; + + close_new_port = true; + close_target_port = true; + } + } else { + close_new_port = true; + } + + if (close_target_port) { + PortRef target_port; + rv = GetPort(port_name, &target_port); + DCHECK(rv == OK); + + ClosePort(target_port); + } + + if (close_new_port) { + PortRef new_port; + rv = GetPort(event.new_port_name, &new_port); + DCHECK(rv == OK); + + ClosePort(new_port); + } + + return ERROR_PORT_STATE_UNEXPECTED; +} + +int Node::AddPortWithName(const PortName& port_name, scoped_refptr port) { + base::AutoLock lock(ports_lock_); + + if (!ports_.insert(std::make_pair(port_name, std::move(port))).second) + return OOPS(ERROR_PORT_EXISTS); // Suggests a bad UUID generator. + + DVLOG(2) << "Created port " << port_name << "@" << name_; + return OK; +} + +void Node::ErasePort(const PortName& port_name) { + base::AutoLock lock(ports_lock_); + ErasePort_Locked(port_name); +} + +void Node::ErasePort_Locked(const PortName& port_name) { + ports_lock_.AssertAcquired(); + ports_.erase(port_name); + DVLOG(2) << "Deleted port " << port_name << "@" << name_; +} + +scoped_refptr Node::GetPort(const PortName& port_name) { + base::AutoLock lock(ports_lock_); + return GetPort_Locked(port_name); +} + +scoped_refptr Node::GetPort_Locked(const PortName& port_name) { + ports_lock_.AssertAcquired(); + auto iter = ports_.find(port_name); + if (iter == ports_.end()) + return nullptr; + +#if (defined(OS_ANDROID) || defined(__ANDROID__)) && defined(ARCH_CPU_ARM64) + // Workaround for https://crbug.com/665869. + base::subtle::MemoryBarrier(); +#endif + + return iter->second; +} + +int Node::SendMessageInternal(const PortRef& port_ref, ScopedMessage* message) { + ScopedMessage& m = *message; + for (size_t i = 0; i < m->num_ports(); ++i) { + if (m->ports()[i] == port_ref.name()) + return ERROR_PORT_CANNOT_SEND_SELF; + } + + Port* port = port_ref.port(); + NodeName peer_node_name; + { + // We must acquire |ports_lock_| before grabbing any port locks, because + // WillSendMessage_Locked may need to lock multiple ports out of order. + base::AutoLock ports_lock(ports_lock_); + base::AutoLock lock(port->lock); + + if (port->state != Port::kReceiving) + return ERROR_PORT_STATE_UNEXPECTED; + + if (port->peer_closed) + return ERROR_PORT_PEER_CLOSED; + + int rv = WillSendMessage_Locked(LockedPort(port), port_ref.name(), m.get()); + if (rv != OK) + return rv; + + // Beyond this point there's no sense in returning anything but OK. Even if + // message forwarding or acceptance fails, there's nothing the embedder can + // do to recover. Assume that failure beyond this point must be treated as a + // transport failure. + + peer_node_name = port->peer_node_name; + } + + if (peer_node_name != name_) { + delegate_->ForwardMessage(peer_node_name, std::move(m)); + return OK; + } + + int rv = AcceptMessage(std::move(m)); + if (rv != OK) { + // See comment above for why we don't return an error in this case. + DVLOG(2) << "AcceptMessage failed: " << rv; + } + + return OK; +} + +int Node::MergePorts_Locked(const PortRef& port0_ref, + const PortRef& port1_ref) { + Port* port0 = port0_ref.port(); + Port* port1 = port1_ref.port(); + + ports_lock_.AssertAcquired(); + port0->lock.AssertAcquired(); + port1->lock.AssertAcquired(); + + CHECK(port0->state == Port::kReceiving); + CHECK(port1->state == Port::kReceiving); + + // Ports cannot be merged with their own receiving peer! + if (port0->peer_node_name == name_ && + port0->peer_port_name == port1_ref.name()) + return ERROR_PORT_STATE_UNEXPECTED; + + if (port1->peer_node_name == name_ && + port1->peer_port_name == port0_ref.name()) + return ERROR_PORT_STATE_UNEXPECTED; + + // Only merge if both ports have never sent a message. + if (port0->next_sequence_num_to_send == kInitialSequenceNum && + port1->next_sequence_num_to_send == kInitialSequenceNum) { + // Swap the ports' peer information and switch them both into buffering + // (eventually proxying) mode. + + std::swap(port0->peer_node_name, port1->peer_node_name); + std::swap(port0->peer_port_name, port1->peer_port_name); + + port0->state = Port::kBuffering; + if (port0->peer_closed) + port0->remove_proxy_on_last_message = true; + + port1->state = Port::kBuffering; + if (port1->peer_closed) + port1->remove_proxy_on_last_message = true; + + int rv1 = BeginProxying_Locked(LockedPort(port0), port0_ref.name()); + int rv2 = BeginProxying_Locked(LockedPort(port1), port1_ref.name()); + + if (rv1 == OK && rv2 == OK) { + // If either merged port had a closed peer, its new peer needs to be + // informed of this. + if (port1->peer_closed) { + ObserveClosureEventData data; + data.last_sequence_num = port0->last_sequence_num_to_receive; + delegate_->ForwardMessage( + port0->peer_node_name, + NewInternalMessage(port0->peer_port_name, + EventType::kObserveClosure, data)); + } + + if (port0->peer_closed) { + ObserveClosureEventData data; + data.last_sequence_num = port1->last_sequence_num_to_receive; + delegate_->ForwardMessage( + port1->peer_node_name, + NewInternalMessage(port1->peer_port_name, + EventType::kObserveClosure, data)); + } + + return OK; + } + + // If either proxy failed to initialize (e.g. had undeliverable messages + // or ended up in a bad state somehow), we keep the system in a consistent + // state by undoing the peer swap. + std::swap(port0->peer_node_name, port1->peer_node_name); + std::swap(port0->peer_port_name, port1->peer_port_name); + port0->remove_proxy_on_last_message = false; + port1->remove_proxy_on_last_message = false; + port0->state = Port::kReceiving; + port1->state = Port::kReceiving; + } + + return ERROR_PORT_STATE_UNEXPECTED; +} + +void Node::WillSendPort(const LockedPort& port, + const NodeName& to_node_name, + PortName* port_name, + PortDescriptor* port_descriptor) { + port->lock.AssertAcquired(); + + PortName local_port_name = *port_name; + + PortName new_port_name; + delegate_->GenerateRandomPortName(&new_port_name); + + // Make sure we don't send messages to the new peer until after we know it + // exists. In the meantime, just buffer messages locally. + DCHECK(port->state == Port::kReceiving); + port->state = Port::kBuffering; + + // If we already know our peer is closed, we already know this proxy can + // be removed once it receives and forwards its last expected message. + if (port->peer_closed) + port->remove_proxy_on_last_message = true; + + *port_name = new_port_name; + + port_descriptor->peer_node_name = port->peer_node_name; + port_descriptor->peer_port_name = port->peer_port_name; + port_descriptor->referring_node_name = name_; + port_descriptor->referring_port_name = local_port_name; + port_descriptor->next_sequence_num_to_send = port->next_sequence_num_to_send; + port_descriptor->next_sequence_num_to_receive = + port->message_queue.next_sequence_num(); + port_descriptor->last_sequence_num_to_receive = + port->last_sequence_num_to_receive; + port_descriptor->peer_closed = port->peer_closed; + memset(port_descriptor->padding, 0, sizeof(port_descriptor->padding)); + + // Configure the local port to point to the new port. + port->peer_node_name = to_node_name; + port->peer_port_name = new_port_name; +} + +int Node::AcceptPort(const PortName& port_name, + const PortDescriptor& port_descriptor) { + scoped_refptr port = make_scoped_refptr( + new Port(port_descriptor.next_sequence_num_to_send, + port_descriptor.next_sequence_num_to_receive)); + port->state = Port::kReceiving; + port->peer_node_name = port_descriptor.peer_node_name; + port->peer_port_name = port_descriptor.peer_port_name; + port->last_sequence_num_to_receive = + port_descriptor.last_sequence_num_to_receive; + port->peer_closed = port_descriptor.peer_closed; + + DVLOG(2) << "Accepting port " << port_name << " [peer_closed=" + << port->peer_closed << "; last_sequence_num_to_receive=" + << port->last_sequence_num_to_receive << "]"; + + // A newly accepted port is not signalable until the message referencing the + // new port finds its way to the consumer (see GetMessage). + port->message_queue.set_signalable(false); + + int rv = AddPortWithName(port_name, std::move(port)); + if (rv != OK) + return rv; + + // Allow referring port to forward messages. + delegate_->ForwardMessage( + port_descriptor.referring_node_name, + NewInternalMessage(port_descriptor.referring_port_name, + EventType::kPortAccepted)); + return OK; +} + +int Node::WillSendMessage_Locked(const LockedPort& port, + const PortName& port_name, + Message* message) { + ports_lock_.AssertAcquired(); + port->lock.AssertAcquired(); + + DCHECK(message); + + // Messages may already have a sequence number if they're being forwarded + // by a proxy. Otherwise, use the next outgoing sequence number. + uint64_t* sequence_num = + &GetMutableEventData(message)->sequence_num; + if (*sequence_num == 0) + *sequence_num = port->next_sequence_num_to_send++; + +#if DCHECK_IS_ON() + std::ostringstream ports_buf; + for (size_t i = 0; i < message->num_ports(); ++i) { + if (i > 0) + ports_buf << ","; + ports_buf << message->ports()[i]; + } +#endif + + if (message->num_ports() > 0) { + // Note: Another thread could be trying to send the same ports, so we need + // to ensure that they are ours to send before we mutate their state. + + std::vector> ports; + ports.resize(message->num_ports()); + + { + for (size_t i = 0; i < message->num_ports(); ++i) { + ports[i] = GetPort_Locked(message->ports()[i]); + DCHECK(ports[i]); + + ports[i]->lock.Acquire(); + int error = OK; + if (ports[i]->state != Port::kReceiving) + error = ERROR_PORT_STATE_UNEXPECTED; + else if (message->ports()[i] == port->peer_port_name) + error = ERROR_PORT_CANNOT_SEND_PEER; + + if (error != OK) { + // Oops, we cannot send this port. + for (size_t j = 0; j <= i; ++j) + ports[i]->lock.Release(); + // Backpedal on the sequence number. + port->next_sequence_num_to_send--; + return error; + } + } + } + + PortDescriptor* port_descriptors = + GetMutablePortDescriptors(GetMutableEventData(message)); + + for (size_t i = 0; i < message->num_ports(); ++i) { + WillSendPort(LockedPort(ports[i].get()), + port->peer_node_name, + message->mutable_ports() + i, + port_descriptors + i); + } + + for (size_t i = 0; i < message->num_ports(); ++i) + ports[i]->lock.Release(); + } + +#if DCHECK_IS_ON() + DVLOG(4) << "Sending message " + << GetEventData(*message)->sequence_num + << " [ports=" << ports_buf.str() << "]" + << " from " << port_name << "@" << name_ + << " to " << port->peer_port_name << "@" << port->peer_node_name; +#endif + + GetMutableEventHeader(message)->port_name = port->peer_port_name; + return OK; +} + +int Node::BeginProxying_Locked(const LockedPort& port, + const PortName& port_name) { + ports_lock_.AssertAcquired(); + port->lock.AssertAcquired(); + + if (port->state != Port::kBuffering) + return OOPS(ERROR_PORT_STATE_UNEXPECTED); + + port->state = Port::kProxying; + + int rv = ForwardMessages_Locked(LockedPort(port), port_name); + if (rv != OK) + return rv; + + // We may have observed closure while buffering. In that case, we can advance + // to removing the proxy without sending out an ObserveProxy message. We + // already know the last expected message, etc. + + if (port->remove_proxy_on_last_message) { + MaybeRemoveProxy_Locked(LockedPort(port), port_name); + + // Make sure we propagate closure to our current peer. + ObserveClosureEventData data; + data.last_sequence_num = port->last_sequence_num_to_receive; + delegate_->ForwardMessage( + port->peer_node_name, + NewInternalMessage(port->peer_port_name, + EventType::kObserveClosure, data)); + } else { + InitiateProxyRemoval(LockedPort(port), port_name); + } + + return OK; +} + +int Node::BeginProxying(PortRef port_ref) { + Port* port = port_ref.port(); + { + base::AutoLock ports_lock(ports_lock_); + base::AutoLock lock(port->lock); + + if (port->state != Port::kBuffering) + return OOPS(ERROR_PORT_STATE_UNEXPECTED); + + port->state = Port::kProxying; + + int rv = ForwardMessages_Locked(LockedPort(port), port_ref.name()); + if (rv != OK) + return rv; + } + + bool should_remove; + NodeName peer_node_name; + ScopedMessage closure_message; + { + base::AutoLock lock(port->lock); + if (port->state != Port::kProxying) + return OOPS(ERROR_PORT_STATE_UNEXPECTED); + + should_remove = port->remove_proxy_on_last_message; + if (should_remove) { + // Make sure we propagate closure to our current peer. + ObserveClosureEventData data; + data.last_sequence_num = port->last_sequence_num_to_receive; + peer_node_name = port->peer_node_name; + closure_message = NewInternalMessage(port->peer_port_name, + EventType::kObserveClosure, data); + } else { + InitiateProxyRemoval(LockedPort(port), port_ref.name()); + } + } + + if (should_remove) { + TryRemoveProxy(port_ref); + delegate_->ForwardMessage(peer_node_name, std::move(closure_message)); + } + + return OK; +} + +int Node::ForwardMessages_Locked(const LockedPort& port, + const PortName &port_name) { + ports_lock_.AssertAcquired(); + port->lock.AssertAcquired(); + + for (;;) { + ScopedMessage message; + port->message_queue.GetNextMessage(&message, nullptr); + if (!message) + break; + + int rv = WillSendMessage_Locked(LockedPort(port), port_name, message.get()); + if (rv != OK) + return rv; + + delegate_->ForwardMessage(port->peer_node_name, std::move(message)); + } + return OK; +} + +void Node::InitiateProxyRemoval(const LockedPort& port, + const PortName& port_name) { + port->lock.AssertAcquired(); + + // To remove this node, we start by notifying the connected graph that we are + // a proxy. This allows whatever port is referencing this node to skip it. + // Eventually, this node will receive ObserveProxyAck (or ObserveClosure if + // the peer was closed in the meantime). + + ObserveProxyEventData data; + data.proxy_node_name = name_; + data.proxy_port_name = port_name; + data.proxy_to_node_name = port->peer_node_name; + data.proxy_to_port_name = port->peer_port_name; + + delegate_->ForwardMessage( + port->peer_node_name, + NewInternalMessage(port->peer_port_name, EventType::kObserveProxy, data)); +} + +void Node::MaybeRemoveProxy_Locked(const LockedPort& port, + const PortName& port_name) { + // |ports_lock_| must be held so we can potentilaly ErasePort_Locked(). + ports_lock_.AssertAcquired(); + port->lock.AssertAcquired(); + + DCHECK(port->state == Port::kProxying); + + // Make sure we have seen ObserveProxyAck before removing the port. + if (!port->remove_proxy_on_last_message) + return; + + if (!CanAcceptMoreMessages(port.get())) { + // This proxy port is done. We can now remove it! + ErasePort_Locked(port_name); + + if (port->send_on_proxy_removal) { + NodeName to_node = port->send_on_proxy_removal->first; + ScopedMessage& message = port->send_on_proxy_removal->second; + + delegate_->ForwardMessage(to_node, std::move(message)); + port->send_on_proxy_removal.reset(); + } + } else { + DVLOG(2) << "Cannot remove port " << port_name << "@" << name_ + << " now; waiting for more messages"; + } +} + +void Node::TryRemoveProxy(PortRef port_ref) { + Port* port = port_ref.port(); + bool should_erase = false; + ScopedMessage msg; + NodeName to_node; + { + base::AutoLock lock(port->lock); + + // Port already removed. Nothing to do. + if (port->state == Port::kClosed) + return; + + DCHECK(port->state == Port::kProxying); + + // Make sure we have seen ObserveProxyAck before removing the port. + if (!port->remove_proxy_on_last_message) + return; + + if (!CanAcceptMoreMessages(port)) { + // This proxy port is done. We can now remove it! + should_erase = true; + + if (port->send_on_proxy_removal) { + to_node = port->send_on_proxy_removal->first; + msg = std::move(port->send_on_proxy_removal->second); + port->send_on_proxy_removal.reset(); + } + } else { + DVLOG(2) << "Cannot remove port " << port_ref.name() << "@" << name_ + << " now; waiting for more messages"; + } + } + + if (should_erase) + ErasePort(port_ref.name()); + + if (msg) + delegate_->ForwardMessage(to_node, std::move(msg)); +} + +void Node::DestroyAllPortsWithPeer(const NodeName& node_name, + const PortName& port_name) { + // Wipes out all ports whose peer node matches |node_name| and whose peer port + // matches |port_name|. If |port_name| is |kInvalidPortName|, only the peer + // node is matched. + + std::vector ports_to_notify; + std::vector dead_proxies_to_broadcast; + std::deque referenced_port_names; + + { + base::AutoLock ports_lock(ports_lock_); + + for (auto iter = ports_.begin(); iter != ports_.end(); ++iter) { + Port* port = iter->second.get(); + { + base::AutoLock port_lock(port->lock); + + if (port->peer_node_name == node_name && + (port_name == kInvalidPortName || + port->peer_port_name == port_name)) { + if (!port->peer_closed) { + // Treat this as immediate peer closure. It's an exceptional + // condition akin to a broken pipe, so we don't care about losing + // messages. + + port->peer_closed = true; + port->last_sequence_num_to_receive = + port->message_queue.next_sequence_num() - 1; + + if (port->state == Port::kReceiving) + ports_to_notify.push_back(PortRef(iter->first, port)); + } + + // We don't expect to forward any further messages, and we don't + // expect to receive a Port{Accepted,Rejected} event. Because we're + // a proxy with no active peer, we cannot use the normal proxy removal + // procedure of forward-propagating an ObserveProxy. Instead we + // broadcast our own death so it can be back-propagated. This is + // inefficient but rare. + if (port->state != Port::kReceiving) { + dead_proxies_to_broadcast.push_back(iter->first); + iter->second->message_queue.GetReferencedPorts( + &referenced_port_names); + } + } + } + } + + for (const auto& proxy_name : dead_proxies_to_broadcast) { + ports_.erase(proxy_name); + DVLOG(2) << "Forcibly deleted port " << proxy_name << "@" << name_; + } + } + + // Wake up any receiving ports who have just observed simulated peer closure. + for (const auto& port : ports_to_notify) + delegate_->PortStatusChanged(port); + + for (const auto& proxy_name : dead_proxies_to_broadcast) { + // Broadcast an event signifying that this proxy is no longer functioning. + ObserveProxyEventData event; + event.proxy_node_name = name_; + event.proxy_port_name = proxy_name; + event.proxy_to_node_name = kInvalidNodeName; + event.proxy_to_port_name = kInvalidPortName; + delegate_->BroadcastMessage(NewInternalMessage( + kInvalidPortName, EventType::kObserveProxy, event)); + + // Also process death locally since the port that points this closed one + // could be on the current node. + // Note: Although this is recursive, only a single port is involved which + // limits the expected branching to 1. + DestroyAllPortsWithPeer(name_, proxy_name); + } + + // Close any ports referenced by the closed proxies. + for (const auto& name : referenced_port_names) { + PortRef ref; + if (GetPort(name, &ref) == OK) + ClosePort(ref); + } +} + +ScopedMessage Node::NewInternalMessage_Helper(const PortName& port_name, + const EventType& type, + const void* data, + size_t num_data_bytes) { + ScopedMessage message; + delegate_->AllocMessage(sizeof(EventHeader) + num_data_bytes, &message); + + EventHeader* header = GetMutableEventHeader(message.get()); + header->port_name = port_name; + header->type = type; + header->padding = 0; + + if (num_data_bytes) + memcpy(header + 1, data, num_data_bytes); + + return message; +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/node.h b/mojo/edk/system/ports/node.h new file mode 100644 index 0000000000000000000000000000000000000000..55b8d27547bed06126b010472513317be14a9b80 --- /dev/null +++ b/mojo/edk/system/ports/node.h @@ -0,0 +1,228 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_PORTS_NODE_H_ +#define MOJO_EDK_SYSTEM_PORTS_NODE_H_ + +#include +#include + +#include +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/ports/event.h" +#include "mojo/edk/system/ports/message.h" +#include "mojo/edk/system/ports/name.h" +#include "mojo/edk/system/ports/port.h" +#include "mojo/edk/system/ports/port_ref.h" +#include "mojo/edk/system/ports/user_data.h" + +#undef SendMessage // Gah, windows + +namespace mojo { +namespace edk { +namespace ports { + +enum : int { + OK = 0, + ERROR_PORT_UNKNOWN = -10, + ERROR_PORT_EXISTS = -11, + ERROR_PORT_STATE_UNEXPECTED = -12, + ERROR_PORT_CANNOT_SEND_SELF = -13, + ERROR_PORT_PEER_CLOSED = -14, + ERROR_PORT_CANNOT_SEND_PEER = -15, + ERROR_NOT_IMPLEMENTED = -100, +}; + +struct PortStatus { + bool has_messages; + bool receiving_messages; + bool peer_closed; +}; + +class MessageFilter; +class NodeDelegate; + +class Node { + public: + enum class ShutdownPolicy { + DONT_ALLOW_LOCAL_PORTS, + ALLOW_LOCAL_PORTS, + }; + + // Does not take ownership of the delegate. + Node(const NodeName& name, NodeDelegate* delegate); + ~Node(); + + // Returns true iff there are no open ports referring to another node or ports + // in the process of being transferred from this node to another. If this + // returns false, then to ensure clean shutdown, it is necessary to keep the + // node alive and continue routing messages to it via AcceptMessage. This + // method may be called again after AcceptMessage to check if the Node is now + // ready to be destroyed. + // + // If |policy| is set to |ShutdownPolicy::ALLOW_LOCAL_PORTS|, this will return + // |true| even if some ports remain alive, as long as none of them are proxies + // to another node. + bool CanShutdownCleanly( + ShutdownPolicy policy = ShutdownPolicy::DONT_ALLOW_LOCAL_PORTS); + + // Lookup the named port. + int GetPort(const PortName& port_name, PortRef* port_ref); + + // Creates a port on this node. Before the port can be used, it must be + // initialized using InitializePort. This method is useful for bootstrapping + // a connection between two nodes. Generally, ports are created using + // CreatePortPair instead. + int CreateUninitializedPort(PortRef* port_ref); + + // Initializes a newly created port. + int InitializePort(const PortRef& port_ref, + const NodeName& peer_node_name, + const PortName& peer_port_name); + + // Generates a new connected pair of ports bound to this node. These ports + // are initialized and ready to go. + int CreatePortPair(PortRef* port0_ref, PortRef* port1_ref); + + // User data associated with the port. + int SetUserData(const PortRef& port_ref, scoped_refptr user_data); + int GetUserData(const PortRef& port_ref, + scoped_refptr* user_data); + + // Prevents further messages from being sent from this port or delivered to + // this port. The port is removed, and the port's peer is notified of the + // closure after it has consumed all pending messages. + int ClosePort(const PortRef& port_ref); + + // Returns the current status of the port. + int GetStatus(const PortRef& port_ref, PortStatus* port_status); + + // Returns the next available message on the specified port or returns a null + // message if there are none available. Returns ERROR_PORT_PEER_CLOSED to + // indicate that this port's peer has closed. In such cases GetMessage may + // be called until it yields a null message, indicating that no more messages + // may be read from the port. + // + // If |filter| is non-null, the next available message is returned only if it + // is matched by the filter. If the provided filter does not match the next + // available message, GetMessage() behaves as if there is no message + // available. Ownership of |filter| is not taken, and it must outlive the + // extent of this call. + int GetMessage(const PortRef& port_ref, + ScopedMessage* message, + MessageFilter* filter); + + // Sends a message from the specified port to its peer. Note that the message + // notification may arrive synchronously (via PortStatusChanged() on the + // delegate) if the peer is local to this Node. + int SendMessage(const PortRef& port_ref, ScopedMessage message); + + // Corresponding to NodeDelegate::ForwardMessage. + int AcceptMessage(ScopedMessage message); + + // Called to merge two ports with each other. If you have two independent + // port pairs A <=> B and C <=> D, the net result of merging B and C is a + // single connected port pair A <=> D. + // + // Note that the behavior of this operation is undefined if either port to be + // merged (B or C above) has ever been read from or written to directly, and + // this must ONLY be called on one side of the merge, though it doesn't matter + // which side. + // + // It is safe for the non-merged peers (A and D above) to be transferred, + // closed, and/or written to before, during, or after the merge. + int MergePorts(const PortRef& port_ref, + const NodeName& destination_node_name, + const PortName& destination_port_name); + + // Like above but merges two ports local to this node. Because both ports are + // local this can also verify that neither port has been written to before the + // merge. If this fails for any reason, both ports are closed. Otherwise OK + // is returned and the ports' receiving peers are connected to each other. + int MergeLocalPorts(const PortRef& port0_ref, const PortRef& port1_ref); + + // Called to inform this node that communication with another node is lost + // indefinitely. This triggers cleanup of ports bound to this node. + int LostConnectionToNode(const NodeName& node_name); + + private: + class LockedPort; + + // Note: Functions that end with _Locked require |ports_lock_| to be held + // before calling. + int OnUserMessage(ScopedMessage message); + int OnPortAccepted(const PortName& port_name); + int OnObserveProxy(const PortName& port_name, + const ObserveProxyEventData& event); + int OnObserveProxyAck(const PortName& port_name, uint64_t last_sequence_num); + int OnObserveClosure(const PortName& port_name, uint64_t last_sequence_num); + int OnMergePort(const PortName& port_name, const MergePortEventData& event); + + int AddPortWithName(const PortName& port_name, scoped_refptr port); + void ErasePort(const PortName& port_name); + void ErasePort_Locked(const PortName& port_name); + scoped_refptr GetPort(const PortName& port_name); + scoped_refptr GetPort_Locked(const PortName& port_name); + + int SendMessageInternal(const PortRef& port_ref, ScopedMessage* message); + int MergePorts_Locked(const PortRef& port0_ref, const PortRef& port1_ref); + void WillSendPort(const LockedPort& port, + const NodeName& to_node_name, + PortName* port_name, + PortDescriptor* port_descriptor); + int AcceptPort(const PortName& port_name, + const PortDescriptor& port_descriptor); + + int WillSendMessage_Locked(const LockedPort& port, + const PortName& port_name, + Message* message); + int BeginProxying_Locked(const LockedPort& port, const PortName& port_name); + int BeginProxying(PortRef port_ref); + int ForwardMessages_Locked(const LockedPort& port, const PortName& port_name); + void InitiateProxyRemoval(const LockedPort& port, const PortName& port_name); + void MaybeRemoveProxy_Locked(const LockedPort& port, + const PortName& port_name); + void TryRemoveProxy(PortRef port_ref); + void DestroyAllPortsWithPeer(const NodeName& node_name, + const PortName& port_name); + + ScopedMessage NewInternalMessage_Helper(const PortName& port_name, + const EventType& type, + const void* data, + size_t num_data_bytes); + + ScopedMessage NewInternalMessage(const PortName& port_name, + const EventType& type) { + return NewInternalMessage_Helper(port_name, type, nullptr, 0); + } + + template + ScopedMessage NewInternalMessage(const PortName& port_name, + const EventType& type, + const EventData& data) { + return NewInternalMessage_Helper(port_name, type, &data, sizeof(data)); + } + + const NodeName name_; + NodeDelegate* const delegate_; + + // Guards |ports_| as well as any operation which needs to hold multiple port + // locks simultaneously. Usage of this is subtle: it must NEVER be acquired + // after a Port lock is acquired, and it must ALWAYS be acquired before + // calling WillSendMessage_Locked or ForwardMessages_Locked. + base::Lock ports_lock_; + std::unordered_map> ports_; + + DISALLOW_COPY_AND_ASSIGN(Node); +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_NODE_H_ diff --git a/mojo/edk/system/ports/node_delegate.h b/mojo/edk/system/ports/node_delegate.h new file mode 100644 index 0000000000000000000000000000000000000000..8547302a5e826e78fe16f3a272672d4b2b9406a0 --- /dev/null +++ b/mojo/edk/system/ports/node_delegate.h @@ -0,0 +1,48 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_PORTS_NODE_DELEGATE_H_ +#define MOJO_EDK_SYSTEM_PORTS_NODE_DELEGATE_H_ + +#include + +#include "mojo/edk/system/ports/message.h" +#include "mojo/edk/system/ports/name.h" +#include "mojo/edk/system/ports/port_ref.h" + +namespace mojo { +namespace edk { +namespace ports { + +class NodeDelegate { + public: + virtual ~NodeDelegate() {} + + // Port names should be difficult to guess. + virtual void GenerateRandomPortName(PortName* port_name) = 0; + + // Allocate a message, including a header that can be used by the Node + // implementation. |num_header_bytes| will be aligned. The newly allocated + // memory need not be zero-filled. + virtual void AllocMessage(size_t num_header_bytes, + ScopedMessage* message) = 0; + + // Forward a message asynchronously to the specified node. This method MUST + // NOT synchronously call any methods on Node. + virtual void ForwardMessage(const NodeName& node, ScopedMessage message) = 0; + + // Broadcast a message to all nodes. + virtual void BroadcastMessage(ScopedMessage message) = 0; + + // Indicates that the port's status has changed recently. Use Node::GetStatus + // to query the latest status of the port. Note, this event could be spurious + // if another thread is simultaneously modifying the status of the port. + virtual void PortStatusChanged(const PortRef& port_ref) = 0; +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_NODE_DELEGATE_H_ diff --git a/mojo/edk/system/ports/port.cc b/mojo/edk/system/ports/port.cc new file mode 100644 index 0000000000000000000000000000000000000000..e4403aed788823a2feac3f894bb68396e94c2efb --- /dev/null +++ b/mojo/edk/system/ports/port.cc @@ -0,0 +1,24 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/ports/port.h" + +namespace mojo { +namespace edk { +namespace ports { + +Port::Port(uint64_t next_sequence_num_to_send, + uint64_t next_sequence_num_to_receive) + : state(kUninitialized), + next_sequence_num_to_send(next_sequence_num_to_send), + last_sequence_num_to_receive(0), + message_queue(next_sequence_num_to_receive), + remove_proxy_on_last_message(false), + peer_closed(false) {} + +Port::~Port() {} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/port.h b/mojo/edk/system/ports/port.h new file mode 100644 index 0000000000000000000000000000000000000000..ea53d43b5f060c22d1ed3560e6982edbb3dc058d --- /dev/null +++ b/mojo/edk/system/ports/port.h @@ -0,0 +1,60 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_PORTS_PORT_H_ +#define MOJO_EDK_SYSTEM_PORTS_PORT_H_ + +#include +#include +#include +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/ports/message_queue.h" +#include "mojo/edk/system/ports/user_data.h" + +namespace mojo { +namespace edk { +namespace ports { + +class Port : public base::RefCountedThreadSafe { + public: + enum State { + kUninitialized, + kReceiving, + kBuffering, + kProxying, + kClosed + }; + + base::Lock lock; + State state; + NodeName peer_node_name; + PortName peer_port_name; + uint64_t next_sequence_num_to_send; + uint64_t last_sequence_num_to_receive; + MessageQueue message_queue; + std::unique_ptr> send_on_proxy_removal; + scoped_refptr user_data; + bool remove_proxy_on_last_message; + bool peer_closed; + + Port(uint64_t next_sequence_num_to_send, + uint64_t next_sequence_num_to_receive); + + private: + friend class base::RefCountedThreadSafe; + + ~Port(); + + DISALLOW_COPY_AND_ASSIGN(Port); +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_PORT_H_ diff --git a/mojo/edk/system/ports/port_ref.cc b/mojo/edk/system/ports/port_ref.cc new file mode 100644 index 0000000000000000000000000000000000000000..675754d488e1fa52dbe17d49d3fae0a3f439baa3 --- /dev/null +++ b/mojo/edk/system/ports/port_ref.cc @@ -0,0 +1,36 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/ports/port_ref.h" + +#include "mojo/edk/system/ports/port.h" + +namespace mojo { +namespace edk { +namespace ports { + +PortRef::~PortRef() { +} + +PortRef::PortRef() { +} + +PortRef::PortRef(const PortName& name, scoped_refptr port) + : name_(name), port_(std::move(port)) {} + +PortRef::PortRef(const PortRef& other) + : name_(other.name_), port_(other.port_) { +} + +PortRef& PortRef::operator=(const PortRef& other) { + if (&other != this) { + name_ = other.name_; + port_ = other.port_; + } + return *this; +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/port_ref.h b/mojo/edk/system/ports/port_ref.h new file mode 100644 index 0000000000000000000000000000000000000000..59036c38691af9dcafec04485aefe19c6458c38b --- /dev/null +++ b/mojo/edk/system/ports/port_ref.h @@ -0,0 +1,41 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_PORTS_PORT_REF_H_ +#define MOJO_EDK_SYSTEM_PORTS_PORT_REF_H_ + +#include "base/memory/ref_counted.h" +#include "mojo/edk/system/ports/name.h" + +namespace mojo { +namespace edk { +namespace ports { + +class Port; +class Node; + +class PortRef { + public: + ~PortRef(); + PortRef(); + PortRef(const PortName& name, scoped_refptr port); + + PortRef(const PortRef& other); + PortRef& operator=(const PortRef& other); + + const PortName& name() const { return name_; } + + private: + friend class Node; + Port* port() const { return port_.get(); } + + PortName name_; + scoped_refptr port_; +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_PORT_REF_H_ diff --git a/mojo/edk/system/ports/ports_unittest.cc b/mojo/edk/system/ports/ports_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..cb48b3eb2f4100ac901db4fead045c2591ba8f9d --- /dev/null +++ b/mojo/edk/system/ports/ports_unittest.cc @@ -0,0 +1,1478 @@ +// Copyright 2016 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. + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/rand_util.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "mojo/edk/system/ports/event.h" +#include "mojo/edk/system/ports/node.h" +#include "mojo/edk/system/ports/node_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace ports { +namespace test { + +namespace { + +bool MessageEquals(const ScopedMessage& message, const base::StringPiece& s) { + return !strcmp(static_cast(message->payload_bytes()), s.data()); +} + +class TestMessage : public Message { + public: + static ScopedMessage NewUserMessage(size_t num_payload_bytes, + size_t num_ports) { + return ScopedMessage(new TestMessage(num_payload_bytes, num_ports)); + } + + TestMessage(size_t num_payload_bytes, size_t num_ports) + : Message(num_payload_bytes, num_ports) { + start_ = new char[num_header_bytes_ + num_ports_bytes_ + num_payload_bytes]; + InitializeUserMessageHeader(start_); + } + + TestMessage(size_t num_header_bytes, + size_t num_payload_bytes, + size_t num_ports_bytes) + : Message(num_header_bytes, + num_payload_bytes, + num_ports_bytes) { + start_ = new char[num_header_bytes + num_payload_bytes + num_ports_bytes]; + } + + ~TestMessage() override { + delete[] start_; + } +}; + +class TestNode; + +class MessageRouter { + public: + virtual ~MessageRouter() {} + + virtual void GeneratePortName(PortName* name) = 0; + virtual void ForwardMessage(TestNode* from_node, + const NodeName& node_name, + ScopedMessage message) = 0; + virtual void BroadcastMessage(TestNode* from_node, ScopedMessage message) = 0; +}; + +class TestNode : public NodeDelegate { + public: + explicit TestNode(uint64_t id) + : node_name_(id, 1), + node_(node_name_, this), + node_thread_(base::StringPrintf("Node %" PRIu64 " thread", id)), + messages_available_event_( + base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED), + idle_event_( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::SIGNALED) { + } + + ~TestNode() override { + StopWhenIdle(); + node_thread_.Stop(); + } + + const NodeName& name() const { return node_name_; } + + // NOTE: Node is thread-safe. + Node& node() { return node_; } + + base::WaitableEvent& idle_event() { return idle_event_; } + + bool IsIdle() { + base::AutoLock lock(lock_); + return started_ && !dispatching_ && + (incoming_messages_.empty() || (block_on_event_ && blocked_)); + } + + void BlockOnEvent(EventType type) { + base::AutoLock lock(lock_); + blocked_event_type_ = type; + block_on_event_ = true; + } + + void Unblock() { + base::AutoLock lock(lock_); + block_on_event_ = false; + messages_available_event_.Signal(); + } + + void Start(MessageRouter* router) { + router_ = router; + node_thread_.Start(); + node_thread_.task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestNode::ProcessMessages, base::Unretained(this))); + } + + void StopWhenIdle() { + base::AutoLock lock(lock_); + should_quit_ = true; + messages_available_event_.Signal(); + } + + void WakeUp() { messages_available_event_.Signal(); } + + int SendStringMessage(const PortRef& port, const std::string& s) { + size_t size = s.size() + 1; + ScopedMessage message = TestMessage::NewUserMessage(size, 0); + memcpy(message->mutable_payload_bytes(), s.data(), size); + return node_.SendMessage(port, std::move(message)); + } + + int SendStringMessageWithPort(const PortRef& port, + const std::string& s, + const PortName& sent_port_name) { + size_t size = s.size() + 1; + ScopedMessage message = TestMessage::NewUserMessage(size, 1); + memcpy(message->mutable_payload_bytes(), s.data(), size); + message->mutable_ports()[0] = sent_port_name; + return node_.SendMessage(port, std::move(message)); + } + + int SendStringMessageWithPort(const PortRef& port, + const std::string& s, + const PortRef& sent_port) { + return SendStringMessageWithPort(port, s, sent_port.name()); + } + + void set_drop_messages(bool value) { + base::AutoLock lock(lock_); + drop_messages_ = value; + } + + void set_save_messages(bool value) { + base::AutoLock lock(lock_); + save_messages_ = value; + } + + bool ReadMessage(const PortRef& port, ScopedMessage* message) { + return node_.GetMessage(port, message, nullptr) == OK && *message; + } + + bool GetSavedMessage(ScopedMessage* message) { + base::AutoLock lock(lock_); + if (saved_messages_.empty()) { + message->reset(); + return false; + } + std::swap(*message, saved_messages_.front()); + saved_messages_.pop(); + return true; + } + + void EnqueueMessage(ScopedMessage message) { + idle_event_.Reset(); + + // NOTE: This may be called from ForwardMessage and thus must not reenter + // |node_|. + base::AutoLock lock(lock_); + incoming_messages_.emplace(std::move(message)); + messages_available_event_.Signal(); + } + + void GenerateRandomPortName(PortName* port_name) override { + DCHECK(router_); + router_->GeneratePortName(port_name); + } + + void AllocMessage(size_t num_header_bytes, ScopedMessage* message) override { + message->reset(new TestMessage(num_header_bytes, 0, 0)); + } + + void ForwardMessage(const NodeName& node_name, + ScopedMessage message) override { + { + base::AutoLock lock(lock_); + if (drop_messages_) { + DVLOG(1) << "Dropping ForwardMessage from node " + << node_name_ << " to " << node_name; + + base::AutoUnlock unlock(lock_); + ClosePortsInMessage(message.get()); + return; + } + } + + DCHECK(router_); + DVLOG(1) << "ForwardMessage from node " + << node_name_ << " to " << node_name; + router_->ForwardMessage(this, node_name, std::move(message)); + } + + void BroadcastMessage(ScopedMessage message) override { + router_->BroadcastMessage(this, std::move(message)); + } + + void PortStatusChanged(const PortRef& port) override { + // The port may be closed, in which case we ignore the notification. + base::AutoLock lock(lock_); + if (!save_messages_) + return; + + for (;;) { + ScopedMessage message; + { + base::AutoUnlock unlock(lock_); + if (!ReadMessage(port, &message)) + break; + } + + saved_messages_.emplace(std::move(message)); + } + } + + void ClosePortsInMessage(Message* message) { + for (size_t i = 0; i < message->num_ports(); ++i) { + PortRef port; + ASSERT_EQ(OK, node_.GetPort(message->ports()[i], &port)); + EXPECT_EQ(OK, node_.ClosePort(port)); + } + } + + private: + void ProcessMessages() { + for (;;) { + messages_available_event_.Wait(); + + base::AutoLock lock(lock_); + + if (should_quit_) + return; + + dispatching_ = true; + while (!incoming_messages_.empty()) { + if (block_on_event_ && + GetEventHeader(*incoming_messages_.front())->type == + blocked_event_type_) { + blocked_ = true; + // Go idle if we hit a blocked event type. + break; + } else { + blocked_ = false; + } + ScopedMessage message = std::move(incoming_messages_.front()); + incoming_messages_.pop(); + + // NOTE: AcceptMessage() can re-enter this object to call any of the + // NodeDelegate interface methods. + base::AutoUnlock unlock(lock_); + node_.AcceptMessage(std::move(message)); + } + + dispatching_ = false; + started_ = true; + idle_event_.Signal(); + }; + } + + const NodeName node_name_; + Node node_; + MessageRouter* router_ = nullptr; + + base::Thread node_thread_; + base::WaitableEvent messages_available_event_; + base::WaitableEvent idle_event_; + + // Guards fields below. + base::Lock lock_; + bool started_ = false; + bool dispatching_ = false; + bool should_quit_ = false; + bool drop_messages_ = false; + bool save_messages_ = false; + bool blocked_ = false; + bool block_on_event_ = false; + EventType blocked_event_type_; + std::queue incoming_messages_; + std::queue saved_messages_; +}; + +class PortsTest : public testing::Test, public MessageRouter { + public: + void AddNode(TestNode* node) { + { + base::AutoLock lock(lock_); + nodes_[node->name()] = node; + } + node->Start(this); + } + + void RemoveNode(TestNode* node) { + { + base::AutoLock lock(lock_); + nodes_.erase(node->name()); + } + + for (const auto& entry : nodes_) + entry.second->node().LostConnectionToNode(node->name()); + } + + // Waits until all known Nodes are idle. Message forwarding and processing + // is handled in such a way that idleness is a stable state: once all nodes in + // the system are idle, they will remain idle until the test explicitly + // initiates some further event (e.g. sending a message, closing a port, or + // removing a Node). + void WaitForIdle() { + for (;;) { + base::AutoLock global_lock(global_lock_); + bool all_nodes_idle = true; + for (const auto& entry : nodes_) { + if (!entry.second->IsIdle()) + all_nodes_idle = false; + entry.second->WakeUp(); + } + if (all_nodes_idle) + return; + + // Wait for any Node to signal that it's idle. + base::AutoUnlock global_unlock(global_lock_); + std::vector events; + for (const auto& entry : nodes_) + events.push_back(&entry.second->idle_event()); + base::WaitableEvent::WaitMany(events.data(), events.size()); + } + } + + void CreatePortPair(TestNode* node0, + PortRef* port0, + TestNode* node1, + PortRef* port1) { + if (node0 == node1) { + EXPECT_EQ(OK, node0->node().CreatePortPair(port0, port1)); + } else { + EXPECT_EQ(OK, node0->node().CreateUninitializedPort(port0)); + EXPECT_EQ(OK, node1->node().CreateUninitializedPort(port1)); + EXPECT_EQ(OK, node0->node().InitializePort(*port0, node1->name(), + port1->name())); + EXPECT_EQ(OK, node1->node().InitializePort(*port1, node0->name(), + port0->name())); + } + } + + private: + // MessageRouter: + void GeneratePortName(PortName* name) override { + base::AutoLock lock(lock_); + name->v1 = next_port_id_++; + name->v2 = 0; + } + + void ForwardMessage(TestNode* from_node, + const NodeName& node_name, + ScopedMessage message) override { + base::AutoLock global_lock(global_lock_); + base::AutoLock lock(lock_); + // Drop messages from nodes that have been removed. + if (nodes_.find(from_node->name()) == nodes_.end()) { + from_node->ClosePortsInMessage(message.get()); + return; + } + + auto it = nodes_.find(node_name); + if (it == nodes_.end()) { + DVLOG(1) << "Node not found: " << node_name; + return; + } + + it->second->EnqueueMessage(std::move(message)); + } + + void BroadcastMessage(TestNode* from_node, ScopedMessage message) override { + base::AutoLock global_lock(global_lock_); + base::AutoLock lock(lock_); + + // Drop messages from nodes that have been removed. + if (nodes_.find(from_node->name()) == nodes_.end()) + return; + + for (const auto& entry : nodes_) { + TestNode* node = entry.second; + // Broadcast doesn't deliver to the local node. + if (node == from_node) + continue; + + // NOTE: We only need to support broadcast of events. Events have no + // payload or ports bytes. + ScopedMessage new_message( + new TestMessage(message->num_header_bytes(), 0, 0)); + memcpy(new_message->mutable_header_bytes(), message->header_bytes(), + message->num_header_bytes()); + node->EnqueueMessage(std::move(new_message)); + } + } + + base::MessageLoop message_loop_; + + // Acquired before any operation which makes a Node busy, and before testing + // if all nodes are idle. + base::Lock global_lock_; + + base::Lock lock_; + uint64_t next_port_id_ = 1; + std::map nodes_; +}; + +} // namespace + +TEST_F(PortsTest, Basic1) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + PortRef a0, a1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&a0, &a1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "hello", a1)); + EXPECT_EQ(OK, node0.node().ClosePort(a0)); + + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, Basic2) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + PortRef b0, b1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&b0, &b1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "hello", b1)); + EXPECT_EQ(OK, node0.SendStringMessage(b0, "hello again")); + + EXPECT_EQ(OK, node0.node().ClosePort(b0)); + + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, Basic3) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + PortRef a0, a1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&a0, &a1)); + + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "hello", a1)); + EXPECT_EQ(OK, node0.SendStringMessage(a0, "hello again")); + + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "foo", a0)); + + PortRef b0, b1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&b0, &b1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "bar", b1)); + EXPECT_EQ(OK, node0.SendStringMessage(b0, "baz")); + + EXPECT_EQ(OK, node0.node().ClosePort(b0)); + + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, LostConnectionToNode1) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + node1.set_drop_messages(true); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + // Transfer a port to node1 and simulate a lost connection to node1. + + PortRef a0, a1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&a0, &a1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "foo", a1)); + + WaitForIdle(); + + RemoveNode(&node1); + + WaitForIdle(); + + EXPECT_EQ(OK, node0.node().ClosePort(a0)); + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, LostConnectionToNode2) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + PortRef a0, a1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&a0, &a1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "take a1", a1)); + + WaitForIdle(); + + node1.set_drop_messages(true); + + RemoveNode(&node1); + + WaitForIdle(); + + // a0 should have eventually detected peer closure after node loss. + ScopedMessage message; + EXPECT_EQ(ERROR_PORT_PEER_CLOSED, + node0.node().GetMessage(a0, &message, nullptr)); + EXPECT_FALSE(message); + + EXPECT_EQ(OK, node0.node().ClosePort(a0)); + + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + + EXPECT_EQ(OK, node1.node().GetMessage(x1, &message, nullptr)); + EXPECT_TRUE(message); + node1.ClosePortsInMessage(message.get()); + + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, LostConnectionToNodeWithSecondaryProxy) { + // Tests that a proxy gets cleaned up when its indirect peer lives on a lost + // node. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + TestNode node2(2); + AddNode(&node2); + + // Create A-B spanning nodes 0 and 1 and C-D spanning 1 and 2. + PortRef A, B, C, D; + CreatePortPair(&node0, &A, &node1, &B); + CreatePortPair(&node1, &C, &node2, &D); + + // Create E-F and send F over A to node 1. + PortRef E, F; + EXPECT_EQ(OK, node0.node().CreatePortPair(&E, &F)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(A, ".", F)); + + WaitForIdle(); + + ScopedMessage message; + ASSERT_TRUE(node1.ReadMessage(B, &message)); + ASSERT_EQ(1u, message->num_ports()); + + EXPECT_EQ(OK, node1.node().GetPort(message->ports()[0], &F)); + + // Send F over C to node 2 and then simulate node 2 loss from node 1. Node 1 + // will trivially become aware of the loss, and this test verifies that the + // port A on node 0 will eventually also become aware of it. + + // Make sure node2 stops processing events when it encounters an ObserveProxy. + node2.BlockOnEvent(EventType::kObserveProxy); + + EXPECT_EQ(OK, node1.SendStringMessageWithPort(C, ".", F)); + WaitForIdle(); + + // Simulate node 1 and 2 disconnecting. + EXPECT_EQ(OK, node1.node().LostConnectionToNode(node2.name())); + + // Let node2 continue processing events and wait for everyone to go idle. + node2.Unblock(); + WaitForIdle(); + + // Port F should be gone. + EXPECT_EQ(ERROR_PORT_UNKNOWN, node1.node().GetPort(F.name(), &F)); + + // Port E should have detected peer closure despite the fact that there is + // no longer a continuous route from F to E over which the event could travel. + PortStatus status; + EXPECT_EQ(OK, node0.node().GetStatus(E, &status)); + EXPECT_TRUE(status.peer_closed); + + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(B)); + EXPECT_EQ(OK, node1.node().ClosePort(C)); + EXPECT_EQ(OK, node0.node().ClosePort(E)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, LostConnectionToNodeWithLocalProxy) { + // Tests that a proxy gets cleaned up when its direct peer lives on a lost + // node and it's predecessor lives on the same node. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef A, B; + CreatePortPair(&node0, &A, &node1, &B); + + PortRef C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&C, &D)); + + // Send D but block node0 on an ObserveProxy event. + node0.BlockOnEvent(EventType::kObserveProxy); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(A, ".", D)); + + // node0 won't collapse the proxy but node1 will receive the message before + // going idle. + WaitForIdle(); + + ScopedMessage message; + ASSERT_TRUE(node1.ReadMessage(B, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef E; + EXPECT_EQ(OK, node1.node().GetPort(message->ports()[0], &E)); + + RemoveNode(&node1); + + node0.Unblock(); + WaitForIdle(); + + // Port C should have detected peer closure. + PortStatus status; + EXPECT_EQ(OK, node0.node().GetStatus(C, &status)); + EXPECT_TRUE(status.peer_closed); + + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(B)); + EXPECT_EQ(OK, node0.node().ClosePort(C)); + EXPECT_EQ(OK, node1.node().ClosePort(E)); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, GetMessage1) { + TestNode node(0); + AddNode(&node); + + PortRef a0, a1; + EXPECT_EQ(OK, node.node().CreatePortPair(&a0, &a1)); + + ScopedMessage message; + EXPECT_EQ(OK, node.node().GetMessage(a0, &message, nullptr)); + EXPECT_FALSE(message); + + EXPECT_EQ(OK, node.node().ClosePort(a1)); + + WaitForIdle(); + + EXPECT_EQ(ERROR_PORT_PEER_CLOSED, + node.node().GetMessage(a0, &message, nullptr)); + EXPECT_FALSE(message); + + EXPECT_EQ(OK, node.node().ClosePort(a0)); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, GetMessage2) { + TestNode node(0); + AddNode(&node); + + PortRef a0, a1; + EXPECT_EQ(OK, node.node().CreatePortPair(&a0, &a1)); + + EXPECT_EQ(OK, node.SendStringMessage(a1, "1")); + + ScopedMessage message; + EXPECT_EQ(OK, node.node().GetMessage(a0, &message, nullptr)); + + ASSERT_TRUE(message); + EXPECT_TRUE(MessageEquals(message, "1")); + + EXPECT_EQ(OK, node.node().ClosePort(a0)); + EXPECT_EQ(OK, node.node().ClosePort(a1)); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, GetMessage3) { + TestNode node(0); + AddNode(&node); + + PortRef a0, a1; + EXPECT_EQ(OK, node.node().CreatePortPair(&a0, &a1)); + + const char* kStrings[] = { + "1", + "2", + "3" + }; + + for (size_t i = 0; i < sizeof(kStrings)/sizeof(kStrings[0]); ++i) + EXPECT_EQ(OK, node.SendStringMessage(a1, kStrings[i])); + + ScopedMessage message; + for (size_t i = 0; i < sizeof(kStrings)/sizeof(kStrings[0]); ++i) { + EXPECT_EQ(OK, node.node().GetMessage(a0, &message, nullptr)); + ASSERT_TRUE(message); + EXPECT_TRUE(MessageEquals(message, kStrings[i])); + } + + EXPECT_EQ(OK, node.node().ClosePort(a0)); + EXPECT_EQ(OK, node.node().ClosePort(a1)); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, Delegation1) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + // In this test, we send a message to a port that has been moved. + + PortRef a0, a1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&a0, &a1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "a1", a1)); + WaitForIdle(); + + ScopedMessage message; + ASSERT_TRUE(node1.ReadMessage(x1, &message)); + ASSERT_EQ(1u, message->num_ports()); + EXPECT_TRUE(MessageEquals(message, "a1")); + + // This is "a1" from the point of view of node1. + PortName a2_name = message->ports()[0]; + EXPECT_EQ(OK, node1.SendStringMessageWithPort(x1, "a2", a2_name)); + EXPECT_EQ(OK, node0.SendStringMessage(a0, "hello")); + + WaitForIdle(); + + ASSERT_TRUE(node0.ReadMessage(x0, &message)); + ASSERT_EQ(1u, message->num_ports()); + EXPECT_TRUE(MessageEquals(message, "a2")); + + // This is "a2" from the point of view of node1. + PortName a3_name = message->ports()[0]; + + PortRef a3; + EXPECT_EQ(OK, node0.node().GetPort(a3_name, &a3)); + + ASSERT_TRUE(node0.ReadMessage(a3, &message)); + EXPECT_EQ(0u, message->num_ports()); + EXPECT_TRUE(MessageEquals(message, "hello")); + + EXPECT_EQ(OK, node0.node().ClosePort(a0)); + EXPECT_EQ(OK, node0.node().ClosePort(a3)); + + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, Delegation2) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + for (int i = 0; i < 100; ++i) { + // Setup pipe a<->b between node0 and node1. + PortRef A, B; + CreatePortPair(&node0, &A, &node1, &B); + + PortRef C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&C, &D)); + + PortRef E, F; + EXPECT_EQ(OK, node0.node().CreatePortPair(&E, &F)); + + node1.set_save_messages(true); + + // Pass D over A to B. + EXPECT_EQ(OK, node0.SendStringMessageWithPort(A, "1", D)); + + // Pass F over C to D. + EXPECT_EQ(OK, node0.SendStringMessageWithPort(C, "1", F)); + + // This message should find its way to node1. + EXPECT_EQ(OK, node0.SendStringMessage(E, "hello")); + + WaitForIdle(); + + EXPECT_EQ(OK, node0.node().ClosePort(C)); + EXPECT_EQ(OK, node0.node().ClosePort(E)); + + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(B)); + + bool got_hello = false; + ScopedMessage message; + while (node1.GetSavedMessage(&message)) { + node1.ClosePortsInMessage(message.get()); + if (MessageEquals(message, "hello")) { + got_hello = true; + break; + } + } + + EXPECT_TRUE(got_hello); + + WaitForIdle(); // Because closing ports may have generated tasks. + } + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, SendUninitialized) { + TestNode node(0); + AddNode(&node); + + PortRef x0; + EXPECT_EQ(OK, node.node().CreateUninitializedPort(&x0)); + EXPECT_EQ(ERROR_PORT_STATE_UNEXPECTED, node.SendStringMessage(x0, "oops")); + EXPECT_EQ(OK, node.node().ClosePort(x0)); + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, SendFailure) { + TestNode node(0); + AddNode(&node); + + node.set_save_messages(true); + + PortRef A, B; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + + // Try to send A over itself. + + EXPECT_EQ(ERROR_PORT_CANNOT_SEND_SELF, + node.SendStringMessageWithPort(A, "oops", A)); + + // Try to send B over A. + + EXPECT_EQ(ERROR_PORT_CANNOT_SEND_PEER, + node.SendStringMessageWithPort(A, "nope", B)); + + // B should be closed immediately. + EXPECT_EQ(ERROR_PORT_UNKNOWN, node.node().GetPort(B.name(), &B)); + + WaitForIdle(); + + // There should have been no messages accepted. + ScopedMessage message; + EXPECT_FALSE(node.GetSavedMessage(&message)); + + EXPECT_EQ(OK, node.node().ClosePort(A)); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, DontLeakUnreceivedPorts) { + TestNode node(0); + AddNode(&node); + + PortRef A, B, C, D; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node.node().CreatePortPair(&C, &D)); + + EXPECT_EQ(OK, node.SendStringMessageWithPort(A, "foo", D)); + + EXPECT_EQ(OK, node.node().ClosePort(C)); + EXPECT_EQ(OK, node.node().ClosePort(A)); + EXPECT_EQ(OK, node.node().ClosePort(B)); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, AllowShutdownWithLocalPortsOpen) { + TestNode node(0); + AddNode(&node); + + PortRef A, B, C, D; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node.node().CreatePortPair(&C, &D)); + + EXPECT_EQ(OK, node.SendStringMessageWithPort(A, "foo", D)); + + ScopedMessage message; + EXPECT_TRUE(node.ReadMessage(B, &message)); + ASSERT_EQ(1u, message->num_ports()); + EXPECT_TRUE(MessageEquals(message, "foo")); + PortRef E; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &E)); + + EXPECT_TRUE( + node.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + + WaitForIdle(); + + EXPECT_TRUE( + node.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + EXPECT_FALSE(node.node().CanShutdownCleanly()); + + EXPECT_EQ(OK, node.node().ClosePort(A)); + EXPECT_EQ(OK, node.node().ClosePort(B)); + EXPECT_EQ(OK, node.node().ClosePort(C)); + EXPECT_EQ(OK, node.node().ClosePort(E)); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, ProxyCollapse1) { + TestNode node(0); + AddNode(&node); + + PortRef A, B; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + + PortRef X, Y; + EXPECT_EQ(OK, node.node().CreatePortPair(&X, &Y)); + + ScopedMessage message; + + // Send B and receive it as C. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", B)); + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef C; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &C)); + + // Send C and receive it as D. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", C)); + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef D; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &D)); + + // Send D and receive it as E. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", D)); + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef E; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &E)); + + EXPECT_EQ(OK, node.node().ClosePort(X)); + EXPECT_EQ(OK, node.node().ClosePort(Y)); + + EXPECT_EQ(OK, node.node().ClosePort(A)); + EXPECT_EQ(OK, node.node().ClosePort(E)); + + // The node should not idle until all proxies are collapsed. + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, ProxyCollapse2) { + TestNode node(0); + AddNode(&node); + + PortRef A, B; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + + PortRef X, Y; + EXPECT_EQ(OK, node.node().CreatePortPair(&X, &Y)); + + ScopedMessage message; + + // Send B and A to create proxies in each direction. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", B)); + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", A)); + + EXPECT_EQ(OK, node.node().ClosePort(X)); + EXPECT_EQ(OK, node.node().ClosePort(Y)); + + // At this point we have a scenario with: + // + // D -> [B] -> C -> [A] + // + // Ensure that the proxies can collapse. The sent ports will be closed + // eventually as a result of Y's closure. + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, SendWithClosedPeer) { + // This tests that if a port is sent when its peer is already known to be + // closed, the newly created port will be aware of that peer closure, and the + // proxy will eventually collapse. + + TestNode node(0); + AddNode(&node); + + // Send a message from A to B, then close A. + PortRef A, B; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node.SendStringMessage(A, "hey")); + EXPECT_EQ(OK, node.node().ClosePort(A)); + + // Now send B over X-Y as new port C. + PortRef X, Y; + EXPECT_EQ(OK, node.node().CreatePortPair(&X, &Y)); + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", B)); + ScopedMessage message; + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef C; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &C)); + + EXPECT_EQ(OK, node.node().ClosePort(X)); + EXPECT_EQ(OK, node.node().ClosePort(Y)); + + WaitForIdle(); + + // C should have received the message originally sent to B, and it should also + // be aware of A's closure. + + ASSERT_TRUE(node.ReadMessage(C, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + PortStatus status; + EXPECT_EQ(OK, node.node().GetStatus(C, &status)); + EXPECT_FALSE(status.receiving_messages); + EXPECT_FALSE(status.has_messages); + EXPECT_TRUE(status.peer_closed); + + node.node().ClosePort(C); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, SendWithClosedPeerSent) { + // This tests that if a port is closed while some number of proxies are still + // routing messages (directly or indirectly) to it, that the peer port is + // eventually notified of the closure, and the dead-end proxies will + // eventually be removed. + + TestNode node(0); + AddNode(&node); + + PortRef X, Y; + EXPECT_EQ(OK, node.node().CreatePortPair(&X, &Y)); + + PortRef A, B; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + + ScopedMessage message; + + // Send A as new port C. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", A)); + + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef C; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &C)); + + // Send C as new port D. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", C)); + + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef D; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &D)); + + // Send a message to B through D, then close D. + EXPECT_EQ(OK, node.SendStringMessage(D, "hey")); + EXPECT_EQ(OK, node.node().ClosePort(D)); + + // Now send B as new port E. + + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", B)); + EXPECT_EQ(OK, node.node().ClosePort(X)); + + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef E; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &E)); + + EXPECT_EQ(OK, node.node().ClosePort(Y)); + + WaitForIdle(); + + // E should receive the message originally sent to B, and it should also be + // aware of D's closure. + + ASSERT_TRUE(node.ReadMessage(E, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + PortStatus status; + EXPECT_EQ(OK, node.node().GetStatus(E, &status)); + EXPECT_FALSE(status.receiving_messages); + EXPECT_FALSE(status.has_messages); + EXPECT_TRUE(status.peer_closed); + + EXPECT_EQ(OK, node.node().ClosePort(E)); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePorts) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + // Write a message on A. + EXPECT_EQ(OK, node0.SendStringMessage(A, "hey")); + + // Initiate a merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + WaitForIdle(); + + // Expect all proxies to be gone once idle. + EXPECT_TRUE( + node0.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + EXPECT_TRUE( + node1.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + + // Expect D to have received the message sent on A. + ScopedMessage message; + ASSERT_TRUE(node1.ReadMessage(D, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(D)); + + // No more ports should be open. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePortWithClosedPeer1) { + // This tests that the right thing happens when initiating a merge on a port + // whose peer has already been closed. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + // Write a message on A. + EXPECT_EQ(OK, node0.SendStringMessage(A, "hey")); + + // Close A. + EXPECT_EQ(OK, node0.node().ClosePort(A)); + + // Initiate a merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + WaitForIdle(); + + // Expect all proxies to be gone once idle. node0 should have no ports since + // A was explicitly closed. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE( + node1.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + + // Expect D to have received the message sent on A. + ScopedMessage message; + ASSERT_TRUE(node1.ReadMessage(D, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + EXPECT_EQ(OK, node1.node().ClosePort(D)); + + // No more ports should be open. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePortWithClosedPeer2) { + // This tests that the right thing happens when merging into a port whose peer + // has already been closed. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + // Write a message on D and close it. + EXPECT_EQ(OK, node0.SendStringMessage(D, "hey")); + EXPECT_EQ(OK, node1.node().ClosePort(D)); + + // Initiate a merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + WaitForIdle(); + + // Expect all proxies to be gone once idle. node1 should have no ports since + // D was explicitly closed. + EXPECT_TRUE( + node0.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); + + // Expect A to have received the message sent on D. + ScopedMessage message; + ASSERT_TRUE(node0.ReadMessage(A, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + EXPECT_EQ(OK, node0.node().ClosePort(A)); + + // No more ports should be open. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePortsWithClosedPeers) { + // This tests that no residual ports are left behind if two ports are merged + // when both of their peers have been closed. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + // Close A and D. + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(D)); + + WaitForIdle(); + + // Initiate a merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + WaitForIdle(); + + // Expect everything to have gone away. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePortsWithMovedPeers) { + // This tests that ports can be merged successfully even if their peers are + // moved around. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + // Set up another pair X-Y for moving ports on node0. + PortRef X, Y; + EXPECT_EQ(OK, node0.node().CreatePortPair(&X, &Y)); + + ScopedMessage message; + + // Move A to new port E. + EXPECT_EQ(OK, node0.SendStringMessageWithPort(X, "foo", A)); + ASSERT_TRUE(node0.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef E; + ASSERT_EQ(OK, node0.node().GetPort(message->ports()[0], &E)); + + EXPECT_EQ(OK, node0.node().ClosePort(X)); + EXPECT_EQ(OK, node0.node().ClosePort(Y)); + + // Write messages on E and D. + EXPECT_EQ(OK, node0.SendStringMessage(E, "hey")); + EXPECT_EQ(OK, node1.SendStringMessage(D, "hi")); + + // Initiate a merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + WaitForIdle(); + + // Expect to receive D's message on E and E's message on D. + ASSERT_TRUE(node0.ReadMessage(E, &message)); + EXPECT_TRUE(MessageEquals(message, "hi")); + ASSERT_TRUE(node1.ReadMessage(D, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + // Close E and D. + EXPECT_EQ(OK, node0.node().ClosePort(E)); + EXPECT_EQ(OK, node1.node().ClosePort(D)); + + WaitForIdle(); + + // Expect everything to have gone away. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePortsFailsGracefully) { + // This tests that the system remains in a well-defined state if something + // goes wrong during port merge. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + ScopedMessage message; + PortRef X, Y; + EXPECT_EQ(OK, node1.node().CreatePortPair(&X, &Y)); + + // Block the merge from proceeding until we can do something stupid with port + // C. This avoids the test logic racing with async merge logic. + node1.BlockOnEvent(EventType::kMergePort); + + // Initiate the merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + // Move C to a new port E. This is not a sane use of Node's public API but + // is still hypothetically possible. It allows us to force a merge failure + // because C will be in an invalid state by the term the merge is processed. + // As a result, B should be closed. + EXPECT_EQ(OK, node1.SendStringMessageWithPort(X, "foo", C)); + + node1.Unblock(); + + ASSERT_TRUE(node1.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef E; + ASSERT_EQ(OK, node1.node().GetPort(message->ports()[0], &E)); + + EXPECT_EQ(OK, node1.node().ClosePort(X)); + EXPECT_EQ(OK, node1.node().ClosePort(Y)); + + WaitForIdle(); + + // C goes away as a result of normal proxy removal. B should have been closed + // cleanly by the failed MergePorts. + EXPECT_EQ(ERROR_PORT_UNKNOWN, node1.node().GetPort(C.name(), &C)); + EXPECT_EQ(ERROR_PORT_UNKNOWN, node0.node().GetPort(B.name(), &B)); + + // Close A, D, and E. + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(D)); + EXPECT_EQ(OK, node1.node().ClosePort(E)); + + WaitForIdle(); + + // Expect everything to have gone away. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +} // namespace test +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/user_data.h b/mojo/edk/system/ports/user_data.h new file mode 100644 index 0000000000000000000000000000000000000000..73e7d17b328b8af6b2c604c9891f62a19f59f1e0 --- /dev/null +++ b/mojo/edk/system/ports/user_data.h @@ -0,0 +1,25 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_PORTS_USER_DATA_H_ +#define MOJO_EDK_SYSTEM_PORTS_USER_DATA_H_ + +#include "base/memory/ref_counted.h" + +namespace mojo { +namespace edk { +namespace ports { + +class UserData : public base::RefCountedThreadSafe { + protected: + friend class base::RefCountedThreadSafe; + + virtual ~UserData() {} +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_USER_DATA_H_ diff --git a/mojo/edk/system/ports_message.cc b/mojo/edk/system/ports_message.cc new file mode 100644 index 0000000000000000000000000000000000000000..5f3e8c012513db8b6af631795c956386a42e5344 --- /dev/null +++ b/mojo/edk/system/ports_message.cc @@ -0,0 +1,62 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/ports_message.h" + +#include "base/memory/ptr_util.h" +#include "mojo/edk/system/node_channel.h" + +namespace mojo { +namespace edk { + +// static +std::unique_ptr PortsMessage::NewUserMessage( + size_t num_payload_bytes, + size_t num_ports, + size_t num_handles) { + return base::WrapUnique( + new PortsMessage(num_payload_bytes, num_ports, num_handles)); +} + +PortsMessage::~PortsMessage() {} + +PortsMessage::PortsMessage(size_t num_payload_bytes, + size_t num_ports, + size_t num_handles) + : ports::Message(num_payload_bytes, num_ports) { + size_t size = num_header_bytes_ + num_ports_bytes_ + num_payload_bytes; + void* ptr; + channel_message_ = NodeChannel::CreatePortsMessage(size, &ptr, num_handles); + InitializeUserMessageHeader(ptr); +} + +PortsMessage::PortsMessage(size_t num_header_bytes, + size_t num_payload_bytes, + size_t num_ports_bytes, + Channel::MessagePtr channel_message) + : ports::Message(num_header_bytes, + num_payload_bytes, + num_ports_bytes) { + if (channel_message) { + channel_message_ = std::move(channel_message); + void* data; + size_t num_data_bytes; + NodeChannel::GetPortsMessageData(channel_message_.get(), &data, + &num_data_bytes); + start_ = static_cast(data); + } else { + // TODO: Clean this up. In practice this branch of the constructor should + // only be reached from Node-internal calls to AllocMessage, which never + // carry ports or non-header bytes. + CHECK_EQ(num_payload_bytes, 0u); + CHECK_EQ(num_ports_bytes, 0u); + void* ptr; + channel_message_ = + NodeChannel::CreatePortsMessage(num_header_bytes, &ptr, 0); + start_ = static_cast(ptr); + } +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports_message.h b/mojo/edk/system/ports_message.h new file mode 100644 index 0000000000000000000000000000000000000000..542b9817003d14da9a478bed6f71d66b5a7b2014 --- /dev/null +++ b/mojo/edk/system/ports_message.h @@ -0,0 +1,69 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_PORTS_MESSAGE_H__ +#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_H__ + +#include +#include + +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/system/channel.h" +#include "mojo/edk/system/ports/message.h" +#include "mojo/edk/system/ports/name.h" + +namespace mojo { +namespace edk { + +class NodeController; + +class PortsMessage : public ports::Message { + public: + static std::unique_ptr NewUserMessage(size_t num_payload_bytes, + size_t num_ports, + size_t num_handles); + + ~PortsMessage() override; + + size_t num_handles() const { return channel_message_->num_handles(); } + bool has_handles() const { return channel_message_->has_handles(); } + + void SetHandles(ScopedPlatformHandleVectorPtr handles) { + channel_message_->SetHandles(std::move(handles)); + } + + ScopedPlatformHandleVectorPtr TakeHandles() { + return channel_message_->TakeHandles(); + } + + Channel::MessagePtr TakeChannelMessage() { + return std::move(channel_message_); + } + + void set_source_node(const ports::NodeName& name) { source_node_ = name; } + const ports::NodeName& source_node() const { return source_node_; } + + private: + friend class NodeController; + + // Construct a new user PortsMessage backed by a new Channel::Message. + PortsMessage(size_t num_payload_bytes, size_t num_ports, size_t num_handles); + + // Construct a new PortsMessage backed by a Channel::Message. If + // |channel_message| is null, a new one is allocated internally. + PortsMessage(size_t num_header_bytes, + size_t num_payload_bytes, + size_t num_ports_bytes, + Channel::MessagePtr channel_message); + + Channel::MessagePtr channel_message_; + + // The node name from which this message was received, if known. + ports::NodeName source_node_ = ports::kInvalidNodeName; +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_MESSAGE_H__ diff --git a/mojo/edk/system/request_context.cc b/mojo/edk/system/request_context.cc new file mode 100644 index 0000000000000000000000000000000000000000..5de65d7b641521fa64ea00e57257c5ae12802fcc --- /dev/null +++ b/mojo/edk/system/request_context.cc @@ -0,0 +1,110 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/request_context.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/thread_local.h" + +namespace mojo { +namespace edk { + +namespace { + +base::LazyInstance>::Leaky + g_current_context = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +RequestContext::RequestContext() : RequestContext(Source::LOCAL_API_CALL) {} + +RequestContext::RequestContext(Source source) + : source_(source), tls_context_(g_current_context.Pointer()) { + // We allow nested RequestContexts to exist as long as they aren't actually + // used for anything. + if (!tls_context_->Get()) + tls_context_->Set(this); +} + +RequestContext::~RequestContext() { + if (IsCurrent()) { + // NOTE: Callbacks invoked by this destructor are allowed to initiate new + // EDK requests on this thread, so we need to reset the thread-local context + // pointer before calling them. We persist the original notification source + // since we're starting over at the bottom of the stack. + tls_context_->Set(nullptr); + + MojoWatcherNotificationFlags flags = MOJO_WATCHER_NOTIFICATION_FLAG_NONE; + if (source_ == Source::SYSTEM) + flags |= MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM; + + // We send all cancellation notifications first. This is necessary because + // it's possible that cancelled watches have other pending notifications + // attached to this RequestContext. + // + // From the application's perspective the watch is cancelled as soon as this + // notification is received, and dispatching the cancellation notification + // updates some internal Watch state to ensure no further notifications + // fire. Because notifications on a single Watch are mutually exclusive, + // this is sufficient to guarantee that MOJO_RESULT_CANCELLED is the last + // notification received; which is the guarantee the API makes. + for (const scoped_refptr& watch : + watch_cancel_finalizers_.container()) { + static const HandleSignalsState closed_state = {0, 0}; + + // Establish a new RequestContext to capture and run any new notifications + // triggered by the callback invocation. + RequestContext inner_context(source_); + watch->InvokeCallback(MOJO_RESULT_CANCELLED, closed_state, flags); + } + + for (const WatchNotifyFinalizer& watch : + watch_notify_finalizers_.container()) { + RequestContext inner_context(source_); + watch.watch->InvokeCallback(watch.result, watch.state, flags); + } + } else { + // It should be impossible for nested contexts to have finalizers. + DCHECK(watch_notify_finalizers_.container().empty()); + DCHECK(watch_cancel_finalizers_.container().empty()); + } +} + +// static +RequestContext* RequestContext::current() { + DCHECK(g_current_context.Pointer()->Get()); + return g_current_context.Pointer()->Get(); +} + +void RequestContext::AddWatchNotifyFinalizer(scoped_refptr watch, + MojoResult result, + const HandleSignalsState& state) { + DCHECK(IsCurrent()); + watch_notify_finalizers_->push_back( + WatchNotifyFinalizer(std::move(watch), result, state)); +} + +void RequestContext::AddWatchCancelFinalizer(scoped_refptr watch) { + DCHECK(IsCurrent()); + watch_cancel_finalizers_->push_back(std::move(watch)); +} + +bool RequestContext::IsCurrent() const { + return tls_context_->Get() == this; +} + +RequestContext::WatchNotifyFinalizer::WatchNotifyFinalizer( + scoped_refptr watch, + MojoResult result, + const HandleSignalsState& state) + : watch(std::move(watch)), result(result), state(state) {} + +RequestContext::WatchNotifyFinalizer::WatchNotifyFinalizer( + const WatchNotifyFinalizer& other) = default; + +RequestContext::WatchNotifyFinalizer::~WatchNotifyFinalizer() {} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/request_context.h b/mojo/edk/system/request_context.h new file mode 100644 index 0000000000000000000000000000000000000000..d1f43bdfbd16ddbfe7af92d8834a564510b752b7 --- /dev/null +++ b/mojo/edk/system/request_context.h @@ -0,0 +1,107 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_REQUEST_CONTEXT_H_ +#define MOJO_EDK_SYSTEM_REQUEST_CONTEXT_H_ + +#include "base/containers/stack_container.h" +#include "base/macros.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/edk/system/watch.h" + +namespace base { +template class ThreadLocalPointer; +} + +namespace mojo { +namespace edk { + +// A RequestContext is a thread-local object which exists for the duration of +// a single system API call. It is constructed immediately upon EDK entry and +// destructed immediately before returning to the caller, after any internal +// locks have been released. +// +// NOTE: It is legal to construct a RequestContext while another one already +// exists on the current thread, but it is not safe to use the nested context +// for any reason. Therefore it is important to always use +// |RequestContext::current()| rather than referring to any local instance +// directly. +class MOJO_SYSTEM_IMPL_EXPORT RequestContext { + public: + // Identifies the source of the current stack frame's RequestContext. + enum class Source { + LOCAL_API_CALL, + SYSTEM, + }; + + // Constructs a RequestContext with a LOCAL_API_CALL Source. + RequestContext(); + + explicit RequestContext(Source source); + ~RequestContext(); + + // Returns the current thread-local RequestContext. + static RequestContext* current(); + + Source source() const { return source_; } + + // Adds a finalizer to this RequestContext corresponding to a watch callback + // which should be triggered in response to some handle state change. If + // the WatcherDispatcher hasn't been closed by the time this RequestContext is + // destroyed, its WatchCallback will be invoked with |result| and |state| + // arguments. + void AddWatchNotifyFinalizer(scoped_refptr watch, + MojoResult result, + const HandleSignalsState& state); + + // Adds a finalizer to this RequestContext corresponding to a watch callback + // which should be triggered to notify of watch cancellation. This appends to + // a separate finalizer list from AddWatchNotifyFinalizer, as pending + // cancellations must always preempt other pending notifications. + void AddWatchCancelFinalizer(scoped_refptr watch); + + private: + // Is this request context the current one? + bool IsCurrent() const; + + struct WatchNotifyFinalizer { + WatchNotifyFinalizer(scoped_refptr watch, + MojoResult result, + const HandleSignalsState& state); + WatchNotifyFinalizer(const WatchNotifyFinalizer& other); + ~WatchNotifyFinalizer(); + + scoped_refptr watch; + MojoResult result; + HandleSignalsState state; + }; + + // NOTE: This upper bound was chosen somewhat arbitrarily after observing some + // rare worst-case behavior in Chrome. A vast majority of RequestContexts only + // ever accumulate 0 or 1 finalizers. + static const size_t kStaticWatchFinalizersCapacity = 8; + + using WatchNotifyFinalizerList = + base::StackVector; + using WatchCancelFinalizerList = + base::StackVector, kStaticWatchFinalizersCapacity>; + + const Source source_; + + WatchNotifyFinalizerList watch_notify_finalizers_; + WatchCancelFinalizerList watch_cancel_finalizers_; + + // Pointer to the TLS context. Although this can easily be accessed via the + // global LazyInstance, accessing a LazyInstance has a large cost relative to + // the rest of this class and its usages. + base::ThreadLocalPointer* tls_context_; + + DISALLOW_COPY_AND_ASSIGN(RequestContext); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_REQUEST_CONTEXT_H_ diff --git a/mojo/edk/system/shared_buffer_dispatcher.cc b/mojo/edk/system/shared_buffer_dispatcher.cc new file mode 100644 index 0000000000000000000000000000000000000000..df391050a2ac2ccda65fcc1b58d3d9c07e1aa869 --- /dev/null +++ b/mojo/edk/system/shared_buffer_dispatcher.cc @@ -0,0 +1,339 @@ +// Copyright 2014 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. + +#include "mojo/edk/system/shared_buffer_dispatcher.h" + +#include +#include + +#include +#include +#include + +#include "base/logging.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/configuration.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/options_validation.h" + +namespace mojo { +namespace edk { + +namespace { + +#pragma pack(push, 1) + +struct SerializedState { + uint64_t num_bytes; + uint32_t flags; + uint32_t padding; +}; + +const uint32_t kSerializedStateFlagsReadOnly = 1 << 0; + +#pragma pack(pop) + +static_assert(sizeof(SerializedState) % 8 == 0, + "Invalid SerializedState size."); + +} // namespace + +// static +const MojoCreateSharedBufferOptions + SharedBufferDispatcher::kDefaultCreateOptions = { + static_cast(sizeof(MojoCreateSharedBufferOptions)), + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE}; + +// static +MojoResult SharedBufferDispatcher::ValidateCreateOptions( + const MojoCreateSharedBufferOptions* in_options, + MojoCreateSharedBufferOptions* out_options) { + const MojoCreateSharedBufferOptionsFlags kKnownFlags = + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE; + + *out_options = kDefaultCreateOptions; + if (!in_options) + return MOJO_RESULT_OK; + + UserOptionsReader reader(in_options); + if (!reader.is_valid()) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (!OPTIONS_STRUCT_HAS_MEMBER(MojoCreateSharedBufferOptions, flags, reader)) + return MOJO_RESULT_OK; + if ((reader.options().flags & ~kKnownFlags)) + return MOJO_RESULT_UNIMPLEMENTED; + out_options->flags = reader.options().flags; + + // Checks for fields beyond |flags|: + + // (Nothing here yet.) + + return MOJO_RESULT_OK; +} + +// static +MojoResult SharedBufferDispatcher::Create( + const MojoCreateSharedBufferOptions& /*validated_options*/, + NodeController* node_controller, + uint64_t num_bytes, + scoped_refptr* result) { + if (!num_bytes) + return MOJO_RESULT_INVALID_ARGUMENT; + if (num_bytes > GetConfiguration().max_shared_memory_num_bytes) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + scoped_refptr shared_buffer; + if (node_controller) { + shared_buffer = + node_controller->CreateSharedBuffer(static_cast(num_bytes)); + } else { + shared_buffer = + PlatformSharedBuffer::Create(static_cast(num_bytes)); + } + if (!shared_buffer) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + *result = CreateInternal(std::move(shared_buffer)); + return MOJO_RESULT_OK; +} + +// static +MojoResult SharedBufferDispatcher::CreateFromPlatformSharedBuffer( + const scoped_refptr& shared_buffer, + scoped_refptr* result) { + if (!shared_buffer) + return MOJO_RESULT_INVALID_ARGUMENT; + + *result = CreateInternal(shared_buffer); + return MOJO_RESULT_OK; +} + +// static +scoped_refptr SharedBufferDispatcher::Deserialize( + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* platform_handles, + size_t num_platform_handles) { + if (num_bytes != sizeof(SerializedState)) { + LOG(ERROR) << "Invalid serialized shared buffer dispatcher (bad size)"; + return nullptr; + } + + const SerializedState* serialization = + static_cast(bytes); + if (!serialization->num_bytes) { + LOG(ERROR) + << "Invalid serialized shared buffer dispatcher (invalid num_bytes)"; + return nullptr; + } + + if (!platform_handles || num_platform_handles != 1 || num_ports) { + LOG(ERROR) + << "Invalid serialized shared buffer dispatcher (missing handles)"; + return nullptr; + } + + // Starts off invalid, which is what we want. + PlatformHandle platform_handle; + // We take ownership of the handle, so we have to invalidate the one in + // |platform_handles|. + std::swap(platform_handle, *platform_handles); + + // Wrapping |platform_handle| in a |ScopedPlatformHandle| means that it'll be + // closed even if creation fails. + bool read_only = (serialization->flags & kSerializedStateFlagsReadOnly); + scoped_refptr shared_buffer( + PlatformSharedBuffer::CreateFromPlatformHandle( + static_cast(serialization->num_bytes), read_only, + ScopedPlatformHandle(platform_handle))); + if (!shared_buffer) { + LOG(ERROR) + << "Invalid serialized shared buffer dispatcher (invalid num_bytes?)"; + return nullptr; + } + + return CreateInternal(std::move(shared_buffer)); +} + +scoped_refptr +SharedBufferDispatcher::PassPlatformSharedBuffer() { + base::AutoLock lock(lock_); + if (!shared_buffer_ || in_transit_) + return nullptr; + + scoped_refptr retval = shared_buffer_; + shared_buffer_ = nullptr; + return retval; +} + +Dispatcher::Type SharedBufferDispatcher::GetType() const { + return Type::SHARED_BUFFER; +} + +MojoResult SharedBufferDispatcher::Close() { + base::AutoLock lock(lock_); + if (in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + shared_buffer_ = nullptr; + return MOJO_RESULT_OK; +} + +MojoResult SharedBufferDispatcher::DuplicateBufferHandle( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr* new_dispatcher) { + MojoDuplicateBufferHandleOptions validated_options; + MojoResult result = ValidateDuplicateOptions(options, &validated_options); + if (result != MOJO_RESULT_OK) + return result; + + // Note: Since this is "duplicate", we keep our ref to |shared_buffer_|. + base::AutoLock lock(lock_); + if (in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + if ((validated_options.flags & + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY) && + (!shared_buffer_->IsReadOnly())) { + // If a read-only duplicate is requested and |shared_buffer_| is not + // read-only, make a read-only duplicate of |shared_buffer_|. + scoped_refptr read_only_buffer = + shared_buffer_->CreateReadOnlyDuplicate(); + if (!read_only_buffer) + return MOJO_RESULT_FAILED_PRECONDITION; + DCHECK(read_only_buffer->IsReadOnly()); + *new_dispatcher = CreateInternal(std::move(read_only_buffer)); + return MOJO_RESULT_OK; + } + + *new_dispatcher = CreateInternal(shared_buffer_); + return MOJO_RESULT_OK; +} + +MojoResult SharedBufferDispatcher::MapBuffer( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + std::unique_ptr* mapping) { + if (offset > static_cast(std::numeric_limits::max())) + return MOJO_RESULT_INVALID_ARGUMENT; + if (num_bytes > static_cast(std::numeric_limits::max())) + return MOJO_RESULT_INVALID_ARGUMENT; + + base::AutoLock lock(lock_); + DCHECK(shared_buffer_); + if (in_transit_ || + !shared_buffer_->IsValidMap(static_cast(offset), + static_cast(num_bytes))) { + return MOJO_RESULT_INVALID_ARGUMENT; + } + + DCHECK(mapping); + *mapping = shared_buffer_->MapNoCheck(static_cast(offset), + static_cast(num_bytes)); + if (!*mapping) { + LOG(ERROR) << "Unable to map: read_only" << shared_buffer_->IsReadOnly(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + return MOJO_RESULT_OK; +} + +void SharedBufferDispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_platform_handles) { + *num_bytes = sizeof(SerializedState); + *num_ports = 0; + *num_platform_handles = 1; +} + +bool SharedBufferDispatcher::EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) { + SerializedState* serialization = + static_cast(destination); + base::AutoLock lock(lock_); + serialization->num_bytes = + static_cast(shared_buffer_->GetNumBytes()); + serialization->flags = + (shared_buffer_->IsReadOnly() ? kSerializedStateFlagsReadOnly : 0); + serialization->padding = 0; + + handle_for_transit_ = shared_buffer_->DuplicatePlatformHandle(); + if (!handle_for_transit_.is_valid()) { + shared_buffer_ = nullptr; + return false; + } + handles[0] = handle_for_transit_.get(); + return true; +} + +bool SharedBufferDispatcher::BeginTransit() { + base::AutoLock lock(lock_); + if (in_transit_) + return false; + in_transit_ = static_cast(shared_buffer_); + return in_transit_; +} + +void SharedBufferDispatcher::CompleteTransitAndClose() { + base::AutoLock lock(lock_); + in_transit_ = false; + shared_buffer_ = nullptr; + ignore_result(handle_for_transit_.release()); +} + +void SharedBufferDispatcher::CancelTransit() { + base::AutoLock lock(lock_); + in_transit_ = false; + handle_for_transit_.reset(); +} + +SharedBufferDispatcher::SharedBufferDispatcher( + scoped_refptr shared_buffer) + : shared_buffer_(shared_buffer) { + DCHECK(shared_buffer_); +} + +SharedBufferDispatcher::~SharedBufferDispatcher() { + DCHECK(!shared_buffer_ && !in_transit_); +} + +// static +MojoResult SharedBufferDispatcher::ValidateDuplicateOptions( + const MojoDuplicateBufferHandleOptions* in_options, + MojoDuplicateBufferHandleOptions* out_options) { + const MojoDuplicateBufferHandleOptionsFlags kKnownFlags = + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY; + static const MojoDuplicateBufferHandleOptions kDefaultOptions = { + static_cast(sizeof(MojoDuplicateBufferHandleOptions)), + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE}; + + *out_options = kDefaultOptions; + if (!in_options) + return MOJO_RESULT_OK; + + UserOptionsReader reader(in_options); + if (!reader.is_valid()) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (!OPTIONS_STRUCT_HAS_MEMBER(MojoDuplicateBufferHandleOptions, flags, + reader)) + return MOJO_RESULT_OK; + if ((reader.options().flags & ~kKnownFlags)) + return MOJO_RESULT_UNIMPLEMENTED; + out_options->flags = reader.options().flags; + + // Checks for fields beyond |flags|: + + // (Nothing here yet.) + + return MOJO_RESULT_OK; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/shared_buffer_dispatcher.h b/mojo/edk/system/shared_buffer_dispatcher.h new file mode 100644 index 0000000000000000000000000000000000000000..6015595317d8d7e469298188ff80dc00542578fc --- /dev/null +++ b/mojo/edk/system/shared_buffer_dispatcher.h @@ -0,0 +1,127 @@ +// Copyright 2014 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 MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { + +namespace edk { +class NodeController; + +class MOJO_SYSTEM_IMPL_EXPORT SharedBufferDispatcher final : public Dispatcher { + public: + // The default options to use for |MojoCreateSharedBuffer()|. (Real uses + // should obtain this via |ValidateCreateOptions()| with a null |in_options|; + // this is exposed directly for testing convenience.) + static const MojoCreateSharedBufferOptions kDefaultCreateOptions; + + // Validates and/or sets default options for |MojoCreateSharedBufferOptions|. + // If non-null, |in_options| must point to a struct of at least + // |in_options->struct_size| bytes. |out_options| must point to a (current) + // |MojoCreateSharedBufferOptions| and will be entirely overwritten on success + // (it may be partly overwritten on failure). + static MojoResult ValidateCreateOptions( + const MojoCreateSharedBufferOptions* in_options, + MojoCreateSharedBufferOptions* out_options); + + // Static factory method: |validated_options| must be validated (obviously). + // On failure, |*result| will be left as-is. + // TODO(vtl): This should probably be made to return a scoped_refptr and have + // a MojoResult out parameter instead. + static MojoResult Create( + const MojoCreateSharedBufferOptions& validated_options, + NodeController* node_controller, + uint64_t num_bytes, + scoped_refptr* result); + + // Create a |SharedBufferDispatcher| from |shared_buffer|. + static MojoResult CreateFromPlatformSharedBuffer( + const scoped_refptr& shared_buffer, + scoped_refptr* result); + + // The "opposite" of SerializeAndClose(). Called by Dispatcher::Deserialize(). + static scoped_refptr Deserialize( + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* platform_handles, + size_t num_platform_handles); + + // Passes the underlying platform shared buffer. This dispatcher must be + // closed after calling this function. + scoped_refptr PassPlatformSharedBuffer(); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + MojoResult DuplicateBufferHandle( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr* new_dispatcher) override; + MojoResult MapBuffer( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + std::unique_ptr* mapping) override; + void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_platform_handles) override; + bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) override; + bool BeginTransit() override; + void CompleteTransitAndClose() override; + void CancelTransit() override; + + private: + static scoped_refptr CreateInternal( + scoped_refptr shared_buffer) { + return make_scoped_refptr( + new SharedBufferDispatcher(std::move(shared_buffer))); + } + + explicit SharedBufferDispatcher( + scoped_refptr shared_buffer); + ~SharedBufferDispatcher() override; + + // Validates and/or sets default options for + // |MojoDuplicateBufferHandleOptions|. If non-null, |in_options| must point to + // a struct of at least |in_options->struct_size| bytes. |out_options| must + // point to a (current) |MojoDuplicateBufferHandleOptions| and will be + // entirely overwritten on success (it may be partly overwritten on failure). + static MojoResult ValidateDuplicateOptions( + const MojoDuplicateBufferHandleOptions* in_options, + MojoDuplicateBufferHandleOptions* out_options); + + // Guards access to |shared_buffer_|. + base::Lock lock_; + + bool in_transit_ = false; + + // We keep a copy of the buffer's platform handle during transit so we can + // close it if something goes wrong. + ScopedPlatformHandle handle_for_transit_; + + scoped_refptr shared_buffer_; + + DISALLOW_COPY_AND_ASSIGN(SharedBufferDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_ diff --git a/mojo/edk/system/shared_buffer_dispatcher_unittest.cc b/mojo/edk/system/shared_buffer_dispatcher_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..c95bdc3b7042c17e9a604368d73697909ba7aad4 --- /dev/null +++ b/mojo/edk/system/shared_buffer_dispatcher_unittest.cc @@ -0,0 +1,312 @@ +// Copyright 2014 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. + +#include "mojo/edk/system/shared_buffer_dispatcher.h" + +#include +#include + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/dispatcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +// NOTE(vtl): There's currently not much to test for in +// |SharedBufferDispatcher::ValidateCreateOptions()|, but the tests should be +// expanded if/when options are added, so I've kept the general form of the +// tests from data_pipe_unittest.cc. + +const uint32_t kSizeOfCreateOptions = sizeof(MojoCreateSharedBufferOptions); + +// Does a cursory sanity check of |validated_options|. Calls +// |ValidateCreateOptions()| on already-validated options. The validated options +// should be valid, and the revalidated copy should be the same. +void RevalidateCreateOptions( + const MojoCreateSharedBufferOptions& validated_options) { + EXPECT_EQ(kSizeOfCreateOptions, validated_options.struct_size); + // Nothing to check for flags. + + MojoCreateSharedBufferOptions revalidated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::ValidateCreateOptions( + &validated_options, &revalidated_options)); + EXPECT_EQ(validated_options.struct_size, revalidated_options.struct_size); + EXPECT_EQ(validated_options.flags, revalidated_options.flags); +} + +class SharedBufferDispatcherTest : public testing::Test { + public: + SharedBufferDispatcherTest() {} + ~SharedBufferDispatcherTest() override {} + + private: + DISALLOW_COPY_AND_ASSIGN(SharedBufferDispatcherTest); +}; + +// Tests valid inputs to |ValidateCreateOptions()|. +TEST_F(SharedBufferDispatcherTest, ValidateCreateOptionsValid) { + // Default options. + { + MojoCreateSharedBufferOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::ValidateCreateOptions( + nullptr, &validated_options)); + RevalidateCreateOptions(validated_options); + } + + // Different flags. + MojoCreateSharedBufferOptionsFlags flags_values[] = { + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE}; + for (size_t i = 0; i < arraysize(flags_values); i++) { + const MojoCreateSharedBufferOptionsFlags flags = flags_values[i]; + + // Different capacities (size 1). + for (uint32_t capacity = 1; capacity <= 100 * 1000 * 1000; capacity *= 10) { + MojoCreateSharedBufferOptions options = { + kSizeOfCreateOptions, // |struct_size|. + flags // |flags|. + }; + MojoCreateSharedBufferOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::ValidateCreateOptions( + &options, &validated_options)) + << capacity; + RevalidateCreateOptions(validated_options); + EXPECT_EQ(options.flags, validated_options.flags); + } + } +} + +TEST_F(SharedBufferDispatcherTest, ValidateCreateOptionsInvalid) { + // Invalid |struct_size|. + { + MojoCreateSharedBufferOptions options = { + 1, // |struct_size|. + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE // |flags|. + }; + MojoCreateSharedBufferOptions unused; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + SharedBufferDispatcher::ValidateCreateOptions( + &options, &unused)); + } + + // Unknown |flags|. + { + MojoCreateSharedBufferOptions options = { + kSizeOfCreateOptions, // |struct_size|. + ~0u // |flags|. + }; + MojoCreateSharedBufferOptions unused; + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + SharedBufferDispatcher::ValidateCreateOptions( + &options, &unused)); + } +} + +TEST_F(SharedBufferDispatcherTest, CreateAndMapBuffer) { + scoped_refptr dispatcher; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, + nullptr, 100, &dispatcher)); + ASSERT_TRUE(dispatcher); + EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher->GetType()); + + // Make a couple of mappings. + std::unique_ptr mapping1; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer( + 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping1)); + ASSERT_TRUE(mapping1); + ASSERT_TRUE(mapping1->GetBase()); + EXPECT_EQ(100u, mapping1->GetLength()); + // Write something. + static_cast(mapping1->GetBase())[50] = 'x'; + + std::unique_ptr mapping2; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer( + 50, 50, MOJO_MAP_BUFFER_FLAG_NONE, &mapping2)); + ASSERT_TRUE(mapping2); + ASSERT_TRUE(mapping2->GetBase()); + EXPECT_EQ(50u, mapping2->GetLength()); + EXPECT_EQ('x', static_cast(mapping2->GetBase())[0]); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); + + // Check that we can still read/write to mappings after the dispatcher has + // gone away. + static_cast(mapping2->GetBase())[1] = 'y'; + EXPECT_EQ('y', static_cast(mapping1->GetBase())[51]); +} + +TEST_F(SharedBufferDispatcherTest, CreateAndMapBufferFromPlatformBuffer) { + scoped_refptr platform_shared_buffer = + PlatformSharedBuffer::Create(100); + ASSERT_TRUE(platform_shared_buffer); + scoped_refptr dispatcher; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::CreateFromPlatformSharedBuffer( + platform_shared_buffer, &dispatcher)); + ASSERT_TRUE(dispatcher); + EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher->GetType()); + + // Make a couple of mappings. + std::unique_ptr mapping1; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer( + 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping1)); + ASSERT_TRUE(mapping1); + ASSERT_TRUE(mapping1->GetBase()); + EXPECT_EQ(100u, mapping1->GetLength()); + // Write something. + static_cast(mapping1->GetBase())[50] = 'x'; + + std::unique_ptr mapping2; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer( + 50, 50, MOJO_MAP_BUFFER_FLAG_NONE, &mapping2)); + ASSERT_TRUE(mapping2); + ASSERT_TRUE(mapping2->GetBase()); + EXPECT_EQ(50u, mapping2->GetLength()); + EXPECT_EQ('x', static_cast(mapping2->GetBase())[0]); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); + + // Check that we can still read/write to mappings after the dispatcher has + // gone away. + static_cast(mapping2->GetBase())[1] = 'y'; + EXPECT_EQ('y', static_cast(mapping1->GetBase())[51]); +} + +TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandle) { + scoped_refptr dispatcher1; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, + nullptr, 100, &dispatcher1)); + + // Map and write something. + std::unique_ptr mapping; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->MapBuffer( + 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + static_cast(mapping->GetBase())[0] = 'x'; + mapping.reset(); + + // Duplicate |dispatcher1| and then close it. + scoped_refptr dispatcher2; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->DuplicateBufferHandle( + nullptr, &dispatcher2)); + ASSERT_TRUE(dispatcher2); + EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher2->GetType()); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close()); + + // Map |dispatcher2| and read something. + EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->MapBuffer( + 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + EXPECT_EQ('x', static_cast(mapping->GetBase())[0]); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close()); +} + +TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsValid) { + scoped_refptr dispatcher1; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, + nullptr, 100, &dispatcher1)); + + MojoDuplicateBufferHandleOptions options[] = { + {sizeof(MojoDuplicateBufferHandleOptions), + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE}, + {sizeof(MojoDuplicateBufferHandleOptions), + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY}, + {sizeof(MojoDuplicateBufferHandleOptionsFlags), ~0u}}; + for (size_t i = 0; i < arraysize(options); i++) { + scoped_refptr dispatcher2; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->DuplicateBufferHandle( + &options[i], &dispatcher2)); + ASSERT_TRUE(dispatcher2); + EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher2->GetType()); + { + std::unique_ptr mapping; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->MapBuffer(0, 100, 0, &mapping)); + } + EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close()); + } + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close()); +} + +TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsInvalid) { + scoped_refptr dispatcher1; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, + nullptr, 100, &dispatcher1)); + + // Invalid |struct_size|. + { + MojoDuplicateBufferHandleOptions options = { + 1u, MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE}; + scoped_refptr dispatcher2; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher1->DuplicateBufferHandle(&options, &dispatcher2)); + EXPECT_FALSE(dispatcher2); + } + + // Unknown |flags|. + { + MojoDuplicateBufferHandleOptions options = { + sizeof(MojoDuplicateBufferHandleOptions), ~0u}; + scoped_refptr dispatcher2; + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + dispatcher1->DuplicateBufferHandle(&options, &dispatcher2)); + EXPECT_FALSE(dispatcher2); + } + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close()); +} + +TEST_F(SharedBufferDispatcherTest, CreateInvalidNumBytes) { + // Size too big. + scoped_refptr dispatcher; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, nullptr, + std::numeric_limits::max(), &dispatcher)); + EXPECT_FALSE(dispatcher); + + // Zero size. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, nullptr, 0, + &dispatcher)); + EXPECT_FALSE(dispatcher); +} + +TEST_F(SharedBufferDispatcherTest, MapBufferInvalidArguments) { + scoped_refptr dispatcher; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, + nullptr, 100, &dispatcher)); + + std::unique_ptr mapping; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher->MapBuffer(0, 101, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + EXPECT_FALSE(mapping); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher->MapBuffer(1, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + EXPECT_FALSE(mapping); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher->MapBuffer(0, 0, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + EXPECT_FALSE(mapping); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/shared_buffer_unittest.cc b/mojo/edk/system/shared_buffer_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..3a728728a57f81b8ec969db788257bd10577e644 --- /dev/null +++ b/mojo/edk/system/shared_buffer_unittest.cc @@ -0,0 +1,318 @@ +// 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. + +#include + +#include +#include + +#include "base/logging.h" +#include "base/memory/shared_memory.h" +#include "base/strings/string_piece.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +using SharedBufferTest = test::MojoTestBase; + +TEST_F(SharedBufferTest, CreateSharedBuffer) { + const std::string message = "hello"; + MojoHandle h = CreateBuffer(message.size()); + WriteToBuffer(h, 0, message); + ExpectBufferContents(h, 0, message); +} + +TEST_F(SharedBufferTest, DuplicateSharedBuffer) { + const std::string message = "hello"; + MojoHandle h = CreateBuffer(message.size()); + WriteToBuffer(h, 0, message); + + MojoHandle dupe = DuplicateBuffer(h, false); + ExpectBufferContents(dupe, 0, message); +} + +TEST_F(SharedBufferTest, PassSharedBufferLocal) { + const std::string message = "hello"; + MojoHandle h = CreateBuffer(message.size()); + WriteToBuffer(h, 0, message); + + MojoHandle dupe = DuplicateBuffer(h, false); + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + + WriteMessageWithHandles(p0, "...", &dupe, 1); + EXPECT_EQ("...", ReadMessageWithHandles(p1, &dupe, 1)); + + ExpectBufferContents(dupe, 0, message); +} + +#if !defined(OS_IOS) + +// Reads a single message with a shared buffer handle, maps the buffer, copies +// the message contents into it, then exits. +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CopyToBufferClient, SharedBufferTest, h) { + MojoHandle b; + std::string message = ReadMessageWithHandles(h, &b, 1); + WriteToBuffer(b, 0, message); + + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_F(SharedBufferTest, PassSharedBufferCrossProcess) { + const std::string message = "hello"; + MojoHandle b = CreateBuffer(message.size()); + + RUN_CHILD_ON_PIPE(CopyToBufferClient, h) + MojoHandle dupe = DuplicateBuffer(b, false); + WriteMessageWithHandles(h, message, &dupe, 1); + WriteMessage(h, "quit"); + END_CHILD() + + ExpectBufferContents(b, 0, message); +} + +// Creates a new buffer, maps it, writes a message contents to it, unmaps it, +// and finally passes it back to the parent. +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateBufferClient, SharedBufferTest, h) { + std::string message = ReadMessage(h); + MojoHandle b = CreateBuffer(message.size()); + WriteToBuffer(b, 0, message); + WriteMessageWithHandles(h, "have a buffer", &b, 1); + + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_F(SharedBufferTest, PassSharedBufferFromChild) { + const std::string message = "hello"; + MojoHandle b; + RUN_CHILD_ON_PIPE(CreateBufferClient, h) + WriteMessage(h, message); + ReadMessageWithHandles(h, &b, 1); + WriteMessage(h, "quit"); + END_CHILD() + + ExpectBufferContents(b, 0, message); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateAndPassBuffer, SharedBufferTest, h) { + // Receive a pipe handle over the primordial pipe. This will be connected to + // another child process. + MojoHandle other_child; + std::string message = ReadMessageWithHandles(h, &other_child, 1); + + // Create a new shared buffer. + MojoHandle b = CreateBuffer(message.size()); + + // Send a copy of the buffer to the parent and the other child. + MojoHandle dupe = DuplicateBuffer(b, false); + WriteMessageWithHandles(h, "", &b, 1); + WriteMessageWithHandles(other_child, "", &dupe, 1); + + EXPECT_EQ("quit", ReadMessage(h)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceiveAndEditBuffer, SharedBufferTest, h) { + // Receive a pipe handle over the primordial pipe. This will be connected to + // another child process (running CreateAndPassBuffer). + MojoHandle other_child; + std::string message = ReadMessageWithHandles(h, &other_child, 1); + + // Receive a shared buffer from the other child. + MojoHandle b; + ReadMessageWithHandles(other_child, &b, 1); + + // Write the message from the parent into the buffer and exit. + WriteToBuffer(b, 0, message); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_F(SharedBufferTest, PassSharedBufferFromChildToChild) { + const std::string message = "hello"; + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + + MojoHandle b; + RUN_CHILD_ON_PIPE(CreateAndPassBuffer, h0) + RUN_CHILD_ON_PIPE(ReceiveAndEditBuffer, h1) + // Send one end of the pipe to each child. The first child will create + // and pass a buffer to the second child and back to us. The second child + // will write our message into the buffer. + WriteMessageWithHandles(h0, message, &p0, 1); + WriteMessageWithHandles(h1, message, &p1, 1); + + // Receive the buffer back from the first child. + ReadMessageWithHandles(h0, &b, 1); + + WriteMessage(h1, "quit"); + END_CHILD() + WriteMessage(h0, "quit"); + END_CHILD() + + // The second child should have written this message. + ExpectBufferContents(b, 0, message); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateAndPassBufferParent, SharedBufferTest, + parent) { + RUN_CHILD_ON_PIPE(CreateAndPassBuffer, child) + // Read a pipe from the parent and forward it to our child. + MojoHandle pipe; + std::string message = ReadMessageWithHandles(parent, &pipe, 1); + + WriteMessageWithHandles(child, message, &pipe, 1); + + // Read a buffer handle from the child and pass it back to the parent. + MojoHandle buffer; + EXPECT_EQ("", ReadMessageWithHandles(child, &buffer, 1)); + WriteMessageWithHandles(parent, "", &buffer, 1); + + EXPECT_EQ("quit", ReadMessage(parent)); + WriteMessage(child, "quit"); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceiveAndEditBufferParent, SharedBufferTest, + parent) { + RUN_CHILD_ON_PIPE(ReceiveAndEditBuffer, child) + // Read a pipe from the parent and forward it to our child. + MojoHandle pipe; + std::string message = ReadMessageWithHandles(parent, &pipe, 1); + WriteMessageWithHandles(child, message, &pipe, 1); + + EXPECT_EQ("quit", ReadMessage(parent)); + WriteMessage(child, "quit"); + END_CHILD() +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +// Android multi-process tests are not executing the new process. This is flaky. +// Passing shared memory handles between cousins is not currently supported on +// OSX. +#define MAYBE_PassHandleBetweenCousins DISABLED_PassHandleBetweenCousins +#else +#define MAYBE_PassHandleBetweenCousins PassHandleBetweenCousins +#endif +TEST_F(SharedBufferTest, MAYBE_PassHandleBetweenCousins) { + const std::string message = "hello"; + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + + // Spawn two children who will each spawn their own child. Make sure the + // grandchildren (cousins to each other) can pass platform handles. + MojoHandle b; + RUN_CHILD_ON_PIPE(CreateAndPassBufferParent, child1) + RUN_CHILD_ON_PIPE(ReceiveAndEditBufferParent, child2) + MojoHandle pipe[2]; + CreateMessagePipe(&pipe[0], &pipe[1]); + + WriteMessageWithHandles(child1, message, &pipe[0], 1); + WriteMessageWithHandles(child2, message, &pipe[1], 1); + + // Receive the buffer back from the first child. + ReadMessageWithHandles(child1, &b, 1); + + WriteMessage(child2, "quit"); + END_CHILD() + WriteMessage(child1, "quit"); + END_CHILD() + + // The second grandchild should have written this message. + ExpectBufferContents(b, 0, message); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReadAndMapWriteSharedBuffer, + SharedBufferTest, h) { + // Receive the shared buffer. + MojoHandle b; + EXPECT_EQ("hello", ReadMessageWithHandles(h, &b, 1)); + + // Read from the bufer. + ExpectBufferContents(b, 0, "hello"); + + // Extract the shared memory handle and try to map it writable. + base::SharedMemoryHandle shm_handle; + bool read_only = false; + ASSERT_EQ(MOJO_RESULT_OK, + PassSharedMemoryHandle(b, &shm_handle, nullptr, &read_only)); + base::SharedMemory shared_memory(shm_handle, false); + EXPECT_TRUE(read_only); + EXPECT_FALSE(shared_memory.Map(1234)); + + EXPECT_EQ("quit", ReadMessage(h)); + WriteMessage(h, "ok"); +} + +#if defined(OS_ANDROID) +// Android multi-process tests are not executing the new process. This is flaky. +#define MAYBE_CreateAndPassReadOnlyBuffer DISABLED_CreateAndPassReadOnlyBuffer +#else +#define MAYBE_CreateAndPassReadOnlyBuffer CreateAndPassReadOnlyBuffer +#endif +TEST_F(SharedBufferTest, MAYBE_CreateAndPassReadOnlyBuffer) { + RUN_CHILD_ON_PIPE(ReadAndMapWriteSharedBuffer, h) + // Create a new shared buffer. + MojoHandle b = CreateBuffer(1234); + WriteToBuffer(b, 0, "hello"); + + // Send a read-only copy of the buffer to the child. + MojoHandle dupe = DuplicateBuffer(b, true /* read_only */); + WriteMessageWithHandles(h, "hello", &dupe, 1); + + WriteMessage(h, "quit"); + EXPECT_EQ("ok", ReadMessage(h)); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateAndPassReadOnlyBuffer, + SharedBufferTest, h) { + // Create a new shared buffer. + MojoHandle b = CreateBuffer(1234); + WriteToBuffer(b, 0, "hello"); + + // Send a read-only copy of the buffer to the parent. + MojoHandle dupe = DuplicateBuffer(b, true /* read_only */); + WriteMessageWithHandles(h, "", &dupe, 1); + + EXPECT_EQ("quit", ReadMessage(h)); + WriteMessage(h, "ok"); +} + +#if defined(OS_ANDROID) +// Android multi-process tests are not executing the new process. This is flaky. +#define MAYBE_CreateAndPassFromChildReadOnlyBuffer \ + DISABLED_CreateAndPassFromChildReadOnlyBuffer +#else +#define MAYBE_CreateAndPassFromChildReadOnlyBuffer \ + CreateAndPassFromChildReadOnlyBuffer +#endif +TEST_F(SharedBufferTest, MAYBE_CreateAndPassFromChildReadOnlyBuffer) { + RUN_CHILD_ON_PIPE(CreateAndPassReadOnlyBuffer, h) + MojoHandle b; + EXPECT_EQ("", ReadMessageWithHandles(h, &b, 1)); + ExpectBufferContents(b, 0, "hello"); + + // Extract the shared memory handle and try to map it writable. + base::SharedMemoryHandle shm_handle; + bool read_only = false; + ASSERT_EQ(MOJO_RESULT_OK, + PassSharedMemoryHandle(b, &shm_handle, nullptr, &read_only)); + base::SharedMemory shared_memory(shm_handle, false); + EXPECT_TRUE(read_only); + EXPECT_FALSE(shared_memory.Map(1234)); + + WriteMessage(h, "quit"); + EXPECT_EQ("ok", ReadMessage(h)); + END_CHILD() +} + +#endif // !defined(OS_IOS) + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/signals_unittest.cc b/mojo/edk/system/signals_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..e8b0cd1914007bc378604afa3bbe4f8325674961 --- /dev/null +++ b/mojo/edk/system/signals_unittest.cc @@ -0,0 +1,76 @@ +// 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. + +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { +namespace edk { +namespace { + +using SignalsTest = test::MojoTestBase; + +TEST_F(SignalsTest, QueryInvalidArguments) { + MojoHandleSignalsState state = {0, 0}; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoQueryHandleSignalsState(MOJO_HANDLE_INVALID, &state)); + + MojoHandle a, b; + CreateMessagePipe(&a, &b); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoQueryHandleSignalsState(a, nullptr)); +} + +TEST_F(SignalsTest, QueryMessagePipeSignals) { + MojoHandleSignalsState state = {0, 0}; + + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + WriteMessage(a, "ok"); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + EXPECT_EQ("ok", ReadMessage(b)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/system_impl_export.h b/mojo/edk/system/system_impl_export.h new file mode 100644 index 0000000000000000000000000000000000000000..5bbf0057b0dc571b115e6cf117203aa825f141b1 --- /dev/null +++ b/mojo/edk/system/system_impl_export.h @@ -0,0 +1,29 @@ +// Copyright 2013 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 MOJO_EDK_SYSTEM_SYSTEM_IMPL_EXPORT_H_ +#define MOJO_EDK_SYSTEM_SYSTEM_IMPL_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION) +#define MOJO_SYSTEM_IMPL_EXPORT __declspec(dllexport) +#else +#define MOJO_SYSTEM_IMPL_EXPORT __declspec(dllimport) +#endif // defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION) +#define MOJO_SYSTEM_IMPL_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_SYSTEM_IMPL_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define MOJO_SYSTEM_IMPL_EXPORT +#endif + +#endif // MOJO_EDK_SYSTEM_SYSTEM_IMPL_EXPORT_H_ diff --git a/mojo/edk/system/test_utils.cc b/mojo/edk/system/test_utils.cc new file mode 100644 index 0000000000000000000000000000000000000000..4a39cf73dae8d09e04559f095fca0168322fa68e --- /dev/null +++ b/mojo/edk/system/test_utils.cc @@ -0,0 +1,76 @@ +// Copyright 2013 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. + +#include "mojo/edk/system/test_utils.h" + +#include + +#include + +#include "base/logging.h" +#include "base/test/test_timeouts.h" +#include "base/threading/platform_thread.h" // For |Sleep()|. +#include "build/build_config.h" + +namespace mojo { +namespace edk { +namespace test { + +MojoDeadline DeadlineFromMilliseconds(unsigned milliseconds) { + return static_cast(milliseconds) * 1000; +} + +MojoDeadline EpsilonDeadline() { +// Originally, our epsilon timeout was 10 ms, which was mostly fine but flaky on +// some Windows bots. I don't recall ever seeing flakes on other bots. At 30 ms +// tests seem reliable on Windows bots, but not at 25 ms. We'd like this timeout +// to be as small as possible (see the description in the .h file). +// +// Currently, |tiny_timeout()| is usually 100 ms (possibly scaled under ASAN, +// etc.). Based on this, set it to (usually be) 30 ms on Windows and 20 ms +// elsewhere. +#if defined(OS_WIN) || defined(OS_ANDROID) + return (TinyDeadline() * 3) / 10; +#else + return (TinyDeadline() * 2) / 10; +#endif +} + +MojoDeadline TinyDeadline() { + return static_cast( + TestTimeouts::tiny_timeout().InMicroseconds()); +} + +MojoDeadline ActionDeadline() { + return static_cast( + TestTimeouts::action_timeout().InMicroseconds()); +} + +void Sleep(MojoDeadline deadline) { + CHECK_LE(deadline, + static_cast(std::numeric_limits::max())); + base::PlatformThread::Sleep( + base::TimeDelta::FromMicroseconds(static_cast(deadline))); +} + +Stopwatch::Stopwatch() { +} + +Stopwatch::~Stopwatch() { +} + +void Stopwatch::Start() { + start_time_ = base::TimeTicks::Now(); +} + +MojoDeadline Stopwatch::Elapsed() { + int64_t result = (base::TimeTicks::Now() - start_time_).InMicroseconds(); + // |DCHECK_GE|, not |CHECK_GE|, since this may be performance-important. + DCHECK_GE(result, 0); + return static_cast(result); +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/test_utils.h b/mojo/edk/system/test_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..1c90dc1717c9276ff158b78cb4ad74f0716657a0 --- /dev/null +++ b/mojo/edk/system/test_utils.h @@ -0,0 +1,59 @@ +// Copyright 2013 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 MOJO_EDK_SYSTEM_TEST_UTILS_H_ +#define MOJO_EDK_SYSTEM_TEST_UTILS_H_ + +#include "base/macros.h" +#include "base/time/time.h" +#include "mojo/public/c/system/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace test { + +MojoDeadline DeadlineFromMilliseconds(unsigned milliseconds); + +// A timeout smaller than |TestTimeouts::tiny_timeout()|, as a |MojoDeadline|. +// Warning: This may lead to flakiness, but this is unavoidable if, e.g., you're +// trying to ensure that functions with timeouts are reasonably accurate. We +// want this to be as small as possible without causing too much flakiness. +MojoDeadline EpsilonDeadline(); + +// |TestTimeouts::tiny_timeout()|, as a |MojoDeadline|. (Expect this to be on +// the order of 100 ms.) +MojoDeadline TinyDeadline(); + +// |TestTimeouts::action_timeout()|, as a |MojoDeadline|. (Expect this to be on +// the order of 10 s.) +MojoDeadline ActionDeadline(); + +// Sleeps for at least the specified duration. +void Sleep(MojoDeadline deadline); + +// Stopwatch ------------------------------------------------------------------- + +// A simple "stopwatch" for measuring time elapsed from a given starting point. +class Stopwatch { + public: + Stopwatch(); + ~Stopwatch(); + + void Start(); + // Returns the amount of time elapsed since the last call to |Start()| (in + // microseconds). + MojoDeadline Elapsed(); + + private: + base::TimeTicks start_time_; + + DISALLOW_COPY_AND_ASSIGN(Stopwatch); +}; + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_TEST_UTILS_H_ diff --git a/mojo/edk/system/watch.cc b/mojo/edk/system/watch.cc new file mode 100644 index 0000000000000000000000000000000000000000..cf08ac37ee5a0d5686fd64e0aaeee486c0c220ca --- /dev/null +++ b/mojo/edk/system/watch.cc @@ -0,0 +1,83 @@ +// 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. + +#include "mojo/edk/system/watch.h" + +#include "mojo/edk/system/request_context.h" +#include "mojo/edk/system/watcher_dispatcher.h" + +namespace mojo { +namespace edk { + +Watch::Watch(const scoped_refptr& watcher, + const scoped_refptr& dispatcher, + uintptr_t context, + MojoHandleSignals signals) + : watcher_(watcher), + dispatcher_(dispatcher), + context_(context), + signals_(signals) {} + +bool Watch::NotifyState(const HandleSignalsState& state, + bool allowed_to_call_callback) { + AssertWatcherLockAcquired(); + + // NOTE: This method must NEVER call into |dispatcher_| directly, because it + // may be called while |dispatcher_| holds a lock. + + MojoResult rv = MOJO_RESULT_SHOULD_WAIT; + RequestContext* const request_context = RequestContext::current(); + if (state.satisfies(signals_)) { + rv = MOJO_RESULT_OK; + if (allowed_to_call_callback && rv != last_known_result_) { + request_context->AddWatchNotifyFinalizer(this, MOJO_RESULT_OK, state); + } + } else if (!state.can_satisfy(signals_)) { + rv = MOJO_RESULT_FAILED_PRECONDITION; + if (allowed_to_call_callback && rv != last_known_result_) { + request_context->AddWatchNotifyFinalizer( + this, MOJO_RESULT_FAILED_PRECONDITION, state); + } + } + + last_known_signals_state_ = + *static_cast(&state); + last_known_result_ = rv; + return ready(); +} + +void Watch::Cancel() { + RequestContext::current()->AddWatchCancelFinalizer(this); +} + +void Watch::InvokeCallback(MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags) { + // We hold the lock through invocation to ensure that only one notification + // callback runs for this context at any given time. + base::AutoLock lock(notification_lock_); + if (result == MOJO_RESULT_CANCELLED) { + // Make sure cancellation is the last notification we dispatch. + DCHECK(!is_cancelled_); + is_cancelled_ = true; + } else if (is_cancelled_) { + return; + } + + // NOTE: This will acquire |watcher_|'s internal lock. It's safe because a + // thread can only enter InvokeCallback() from within a RequestContext + // destructor where no dispatcher locks are held. + watcher_->InvokeWatchCallback(context_, result, state, flags); +} + +Watch::~Watch() {} + +#if DCHECK_IS_ON() +void Watch::AssertWatcherLockAcquired() const { + watcher_->lock_.AssertAcquired(); +} +#endif + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/watch.h b/mojo/edk/system/watch.h new file mode 100644 index 0000000000000000000000000000000000000000..f277de991767d49d95c1239df1f335b3c90ff7c7 --- /dev/null +++ b/mojo/edk/system/watch.h @@ -0,0 +1,124 @@ +// 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. + +#ifndef MOJO_EDK_SYSTEM_WATCH_H_ +#define MOJO_EDK_SYSTEM_WATCH_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/atomic_flag.h" +#include "mojo/edk/system/handle_signals_state.h" + +namespace mojo { +namespace edk { + +class Dispatcher; +class WatcherDispatcher; + +// Encapsulates the state associated with a single watch context within a +// watcher. +// +// Every Watch has its own cancellation state, and is captured by RequestContext +// notification finalizers to avoid redundant context resolution during +// finalizer execution. +class Watch : public base::RefCountedThreadSafe { + public: + // Constructs a Watch which represents a watch within |watcher| associated + // with |context|, watching |dispatcher| for |signals|. + Watch(const scoped_refptr& watcher, + const scoped_refptr& dispatcher, + uintptr_t context, + MojoHandleSignals signals); + + // Notifies the Watch of a potential state change. + // + // If |allowed_to_call_callback| is true, this may add a notification + // finalizer to the current RequestContext to invoke the watcher's callback + // with this watch's context. See return values below. + // + // This is called directly by WatcherDispatcher whenever the Watch's observed + // dispatcher notifies the WatcherDispatcher of a state change. + // + // Returns |true| if the Watch entered or remains in a ready state as a result + // of the state change. If |allowed_to_call_callback| was true in this case, + // the Watch will have also attached a notification finalizer to the current + // RequestContext. + // + // Returns |false| if the + bool NotifyState(const HandleSignalsState& state, + bool allowed_to_call_callback); + + // Notifies the watch of cancellation ASAP. This will always be the last + // notification sent for the watch. + void Cancel(); + + // Finalizer method for RequestContexts. This method is invoked once for every + // notification finalizer added to a RequestContext by this object. This calls + // down into the WatcherDispatcher to do the actual notification call. + void InvokeCallback(MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags); + + const scoped_refptr& dispatcher() const { return dispatcher_; } + uintptr_t context() const { return context_; } + + MojoResult last_known_result() const { + AssertWatcherLockAcquired(); + return last_known_result_; + } + + MojoHandleSignalsState last_known_signals_state() const { + AssertWatcherLockAcquired(); + return last_known_signals_state_; + } + + bool ready() const { + AssertWatcherLockAcquired(); + return last_known_result_ == MOJO_RESULT_OK || + last_known_result_ == MOJO_RESULT_FAILED_PRECONDITION; + } + + private: + friend class base::RefCountedThreadSafe; + + ~Watch(); + +#if DCHECK_IS_ON() + void AssertWatcherLockAcquired() const; +#else + void AssertWatcherLockAcquired() const {} +#endif + + const scoped_refptr watcher_; + const scoped_refptr dispatcher_; + const uintptr_t context_; + const MojoHandleSignals signals_; + + // The result code with which this Watch would notify if currently armed, + // based on the last known signaling state of |dispatcher_|. Guarded by the + // owning WatcherDispatcher's lock. + MojoResult last_known_result_ = MOJO_RESULT_UNKNOWN; + + // The last known signaling state of |dispatcher_|. Guarded by the owning + // WatcherDispatcher's lock. + MojoHandleSignalsState last_known_signals_state_ = {0, 0}; + + // Guards |is_cancelled_| below and mutually excludes individual watch + // notification executions for this same watch context. + // + // Note that this should only be acquired from a RequestContext finalizer to + // ensure that no other internal locks are already held. + base::Lock notification_lock_; + + // Guarded by |notification_lock_|. + bool is_cancelled_ = false; + + DISALLOW_COPY_AND_ASSIGN(Watch); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_WATCH_H_ diff --git a/mojo/edk/system/watcher_dispatcher.cc b/mojo/edk/system/watcher_dispatcher.cc new file mode 100644 index 0000000000000000000000000000000000000000..409dd2a9229cff2cfa5041826545bd7f08544c69 --- /dev/null +++ b/mojo/edk/system/watcher_dispatcher.cc @@ -0,0 +1,232 @@ +// 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. + +#include "mojo/edk/system/watcher_dispatcher.h" + +#include +#include +#include + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "mojo/edk/system/watch.h" + +namespace mojo { +namespace edk { + +WatcherDispatcher::WatcherDispatcher(MojoWatcherCallback callback) + : callback_(callback) {} + +void WatcherDispatcher::NotifyHandleState(Dispatcher* dispatcher, + const HandleSignalsState& state) { + base::AutoLock lock(lock_); + auto it = watched_handles_.find(dispatcher); + if (it == watched_handles_.end()) + return; + + // Maybe fire a notification to the watch assoicated with this dispatcher, + // provided we're armed it cares about the new state. + if (it->second->NotifyState(state, armed_)) { + ready_watches_.insert(it->second.get()); + + // If we were armed and got here, we notified the watch. Disarm. + armed_ = false; + } else { + ready_watches_.erase(it->second.get()); + } +} + +void WatcherDispatcher::NotifyHandleClosed(Dispatcher* dispatcher) { + scoped_refptr watch; + { + base::AutoLock lock(lock_); + auto it = watched_handles_.find(dispatcher); + if (it == watched_handles_.end()) + return; + + watch = std::move(it->second); + + // Wipe out all state associated with the closed dispatcher. + watches_.erase(watch->context()); + ready_watches_.erase(watch.get()); + watched_handles_.erase(it); + } + + // NOTE: It's important that this is called outside of |lock_| since it + // acquires internal Watch locks. + watch->Cancel(); +} + +void WatcherDispatcher::InvokeWatchCallback( + uintptr_t context, + MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags) { + { + // We avoid holding the lock during dispatch. It's OK for notification + // callbacks to close this watcher, and it's OK for notifications to race + // with closure, if for example the watcher is closed from another thread + // between this test and the invocation of |callback_| below. + // + // Because cancellation synchronously blocks all future notifications, and + // because notifications themselves are mutually exclusive for any given + // context, we still guarantee that a single MOJO_RESULT_CANCELLED result + // is the last notification received for any given context. + // + // This guarantee is sufficient to make safe, synchronized, per-context + // state management possible in user code. + base::AutoLock lock(lock_); + if (closed_ && result != MOJO_RESULT_CANCELLED) + return; + } + + callback_(context, result, static_cast(state), flags); +} + +Dispatcher::Type WatcherDispatcher::GetType() const { + return Type::WATCHER; +} + +MojoResult WatcherDispatcher::Close() { + // We swap out all the watched handle information onto the stack so we can + // call into their dispatchers without our own lock held. + std::map> watches; + { + base::AutoLock lock(lock_); + DCHECK(!closed_); + closed_ = true; + std::swap(watches, watches_); + watched_handles_.clear(); + } + + // Remove all refs from our watched dispatchers and fire cancellations. + for (auto& entry : watches) { + entry.second->dispatcher()->RemoveWatcherRef(this, entry.first); + entry.second->Cancel(); + } + + return MOJO_RESULT_OK; +} + +MojoResult WatcherDispatcher::WatchDispatcher( + scoped_refptr dispatcher, + MojoHandleSignals signals, + uintptr_t context) { + // NOTE: Because it's critical to avoid acquiring any other dispatcher locks + // while |lock_| is held, we defer adding oursevles to the dispatcher until + // after we've updated all our own relevant state and released |lock_|. + { + base::AutoLock lock(lock_); + if (watches_.count(context) || watched_handles_.count(dispatcher.get())) + return MOJO_RESULT_ALREADY_EXISTS; + + scoped_refptr watch = new Watch(this, dispatcher, context, signals); + watches_.insert({context, watch}); + auto result = + watched_handles_.insert(std::make_pair(dispatcher.get(), watch)); + DCHECK(result.second); + } + + MojoResult rv = dispatcher->AddWatcherRef(this, context); + if (rv != MOJO_RESULT_OK) { + // Oops. This was not a valid handle to watch. Undo the above work and + // fail gracefully. + base::AutoLock lock(lock_); + watches_.erase(context); + watched_handles_.erase(dispatcher.get()); + return rv; + } + + return MOJO_RESULT_OK; +} + +MojoResult WatcherDispatcher::CancelWatch(uintptr_t context) { + // We may remove the last stored ref to the Watch below, so we retain + // a reference on the stack. + scoped_refptr watch; + { + base::AutoLock lock(lock_); + auto it = watches_.find(context); + if (it == watches_.end()) + return MOJO_RESULT_NOT_FOUND; + watch = it->second; + watches_.erase(it); + } + + // Mark the watch as cancelled so no further notifications get through. + watch->Cancel(); + + // We remove the watcher ref for this context before updating any more + // internal watcher state, ensuring that we don't receiving further + // notifications for this context. + watch->dispatcher()->RemoveWatcherRef(this, context); + + { + base::AutoLock lock(lock_); + auto handle_it = watched_handles_.find(watch->dispatcher().get()); + DCHECK(handle_it != watched_handles_.end()); + ready_watches_.erase(handle_it->second.get()); + watched_handles_.erase(handle_it); + } + + return MOJO_RESULT_OK; +} + +MojoResult WatcherDispatcher::Arm( + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + base::AutoLock lock(lock_); + if (num_ready_contexts && + (!ready_contexts || !ready_results || !ready_signals_states)) { + return MOJO_RESULT_INVALID_ARGUMENT; + } + + if (watched_handles_.empty()) + return MOJO_RESULT_NOT_FOUND; + + if (ready_watches_.empty()) { + // Fast path: No watches are ready to notify, so we're done. + armed_ = true; + return MOJO_RESULT_OK; + } + + if (num_ready_contexts) { + DCHECK_LE(ready_watches_.size(), std::numeric_limits::max()); + *num_ready_contexts = std::min( + *num_ready_contexts, static_cast(ready_watches_.size())); + + WatchSet::const_iterator next_ready_iter = ready_watches_.begin(); + if (last_watch_to_block_arming_) { + // Find the next watch to notify in simple round-robin order on the + // |ready_watches_| map, wrapping around to the beginning if necessary. + next_ready_iter = ready_watches_.find(last_watch_to_block_arming_); + if (next_ready_iter != ready_watches_.end()) + ++next_ready_iter; + if (next_ready_iter == ready_watches_.end()) + next_ready_iter = ready_watches_.begin(); + } + + for (size_t i = 0; i < *num_ready_contexts; ++i) { + const Watch* const watch = *next_ready_iter; + ready_contexts[i] = watch->context(); + ready_results[i] = watch->last_known_result(); + ready_signals_states[i] = watch->last_known_signals_state(); + + // Iterate and wrap around. + last_watch_to_block_arming_ = watch; + ++next_ready_iter; + if (next_ready_iter == ready_watches_.end()) + next_ready_iter = ready_watches_.begin(); + } + } + + return MOJO_RESULT_FAILED_PRECONDITION; +} + +WatcherDispatcher::~WatcherDispatcher() {} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/watcher_dispatcher.h b/mojo/edk/system/watcher_dispatcher.h new file mode 100644 index 0000000000000000000000000000000000000000..605a3150cc1acea51382d5ee6023503492e3f95a --- /dev/null +++ b/mojo/edk/system/watcher_dispatcher.h @@ -0,0 +1,101 @@ +// 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. + +#ifndef MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_ + +#include +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/watcher.h" + +namespace mojo { +namespace edk { + +class Watch; + +// The dispatcher type which backs watcher handles. +class WatcherDispatcher : public Dispatcher { + public: + // Constructs a new WatcherDispatcher which invokes |callback| when a + // registered watch observes some relevant state change. + explicit WatcherDispatcher(MojoWatcherCallback callback); + + // Methods used by watched dispatchers to notify watchers of events. + void NotifyHandleState(Dispatcher* dispatcher, + const HandleSignalsState& state); + void NotifyHandleClosed(Dispatcher* dispatcher); + + // Method used by RequestContext (indirectly, via Watch) to complete + // notification operations from a safe stack frame to avoid reentrancy. + void InvokeWatchCallback(uintptr_t context, + MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + MojoResult WatchDispatcher(scoped_refptr dispatcher, + MojoHandleSignals signals, + uintptr_t context) override; + MojoResult CancelWatch(uintptr_t context) override; + MojoResult Arm(uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) override; + + private: + friend class Watch; + + using WatchSet = std::set; + + ~WatcherDispatcher() override; + + const MojoWatcherCallback callback_; + + // Guards access to the fields below. + // + // NOTE: This may be acquired while holding another dispatcher's lock, as + // watched dispatchers call into WatcherDispatcher methods which lock this + // when issuing state change notifications. WatcherDispatcher must therefore + // take caution to NEVER acquire other dispatcher locks while this is held. + base::Lock lock_; + + bool armed_ = false; + bool closed_ = false; + + // A mapping from context to Watch. + std::map> watches_; + + // A mapping from watched dispatcher to Watch. + std::map> watched_handles_; + + // The set of all Watch instances which are currently ready to signal. This is + // used for efficient arming behavior, as it allows for O(1) discovery of + // whether or not arming can succeed and quick determination of who's + // responsible if it can't. + WatchSet ready_watches_; + + // Tracks the last Watch whose state was returned by Arm(). This is used to + // ensure consistent round-robin behavior in the event that multiple Watches + // remain ready over the span of several Arm() attempts. + // + // NOTE: This pointer is only used to index |ready_watches_| and may point to + // an invalid object. It must therefore never be dereferenced. + const Watch* last_watch_to_block_arming_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(WatcherDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_ diff --git a/mojo/edk/system/watcher_set.cc b/mojo/edk/system/watcher_set.cc new file mode 100644 index 0000000000000000000000000000000000000000..0355b58795f2287b1c845b06bf17ed530eaebaf0 --- /dev/null +++ b/mojo/edk/system/watcher_set.cc @@ -0,0 +1,82 @@ +// Copyright 2016 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. + +#include "mojo/edk/system/watcher_set.h" + +#include + +namespace mojo { +namespace edk { + +WatcherSet::WatcherSet(Dispatcher* owner) : owner_(owner) {} + +WatcherSet::~WatcherSet() = default; + +void WatcherSet::NotifyState(const HandleSignalsState& state) { + // Avoid notifying watchers if they have already seen this state. + if (last_known_state_.has_value() && state.equals(last_known_state_.value())) + return; + last_known_state_ = state; + for (const auto& entry : watchers_) + entry.first->NotifyHandleState(owner_, state); +} + +void WatcherSet::NotifyClosed() { + for (const auto& entry : watchers_) + entry.first->NotifyHandleClosed(owner_); +} + +MojoResult WatcherSet::Add(const scoped_refptr& watcher, + uintptr_t context, + const HandleSignalsState& current_state) { + auto it = watchers_.find(watcher.get()); + if (it == watchers_.end()) { + auto result = + watchers_.insert(std::make_pair(watcher.get(), Entry{watcher})); + it = result.first; + } + + if (!it->second.contexts.insert(context).second) + return MOJO_RESULT_ALREADY_EXISTS; + + if (last_known_state_.has_value() && + !current_state.equals(last_known_state_.value())) { + // This new state may be relevant to everyone, in which case we just + // notify everyone. + NotifyState(current_state); + } else { + // Otherwise only notify the newly added Watcher. + watcher->NotifyHandleState(owner_, current_state); + } + return MOJO_RESULT_OK; +} + +MojoResult WatcherSet::Remove(WatcherDispatcher* watcher, uintptr_t context) { + auto it = watchers_.find(watcher); + if (it == watchers_.end()) + return MOJO_RESULT_NOT_FOUND; + + ContextSet& contexts = it->second.contexts; + auto context_it = contexts.find(context); + if (context_it == contexts.end()) + return MOJO_RESULT_NOT_FOUND; + + contexts.erase(context_it); + if (contexts.empty()) + watchers_.erase(it); + + return MOJO_RESULT_OK; +} + +WatcherSet::Entry::Entry(const scoped_refptr& dispatcher) + : dispatcher(dispatcher) {} + +WatcherSet::Entry::Entry(Entry&& other) = default; + +WatcherSet::Entry::~Entry() = default; + +WatcherSet::Entry& WatcherSet::Entry::operator=(Entry&& other) = default; + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/watcher_set.h b/mojo/edk/system/watcher_set.h new file mode 100644 index 0000000000000000000000000000000000000000..2b7ef2c5ac1050b9c00bbf3c97468715754525f7 --- /dev/null +++ b/mojo/edk/system/watcher_set.h @@ -0,0 +1,71 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_WATCHER_SET_H_ +#define MOJO_EDK_SYSTEM_WATCHER_SET_H_ + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/optional.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/watcher_dispatcher.h" + +namespace mojo { +namespace edk { + +// A WatcherSet maintains a set of references to WatcherDispatchers to be +// notified when a handle changes state. +// +// Dispatchers which may be watched by a watcher should own a WatcherSet and +// notify it of all relevant state changes. +class WatcherSet { + public: + // |owner| is the Dispatcher who owns this WatcherSet. + explicit WatcherSet(Dispatcher* owner); + ~WatcherSet(); + + // Notifies all watchers of the handle's current signals state. + void NotifyState(const HandleSignalsState& state); + + // Notifies all watchers that this handle has been closed. + void NotifyClosed(); + + // Adds a new watcher+context. + MojoResult Add(const scoped_refptr& watcher, + uintptr_t context, + const HandleSignalsState& current_state); + + // Removes a watcher+context. + MojoResult Remove(WatcherDispatcher* watcher, uintptr_t context); + + private: + using ContextSet = std::set; + + struct Entry { + Entry(const scoped_refptr& dispatcher); + Entry(Entry&& other); + ~Entry(); + + Entry& operator=(Entry&& other); + + scoped_refptr dispatcher; + ContextSet contexts; + + private: + DISALLOW_COPY_AND_ASSIGN(Entry); + }; + + Dispatcher* const owner_; + std::map watchers_; + base::Optional last_known_state_; + + DISALLOW_COPY_AND_ASSIGN(WatcherSet); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_WATCHER_SET_H_ diff --git a/mojo/edk/system/watcher_unittest.cc b/mojo/edk/system/watcher_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..dd396cd90592caa658563ac737a1fa8e102c610f --- /dev/null +++ b/mojo/edk/system/watcher_unittest.cc @@ -0,0 +1,1637 @@ +// 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. + +#include + +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "base/time/time.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/c/system/watcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +using WatcherTest = test::MojoTestBase; + +class WatchHelper { + public: + using ContextCallback = + base::Callback; + + WatchHelper() {} + ~WatchHelper() {} + + MojoResult CreateWatcher(MojoHandle* handle) { + return MojoCreateWatcher(&Notify, handle); + } + + uintptr_t CreateContext(const ContextCallback& callback) { + return CreateContextWithCancel(callback, base::Closure()); + } + + uintptr_t CreateContextWithCancel(const ContextCallback& callback, + const base::Closure& cancel_callback) { + auto context = base::MakeUnique(callback); + NotificationContext* raw_context = context.get(); + raw_context->SetCancelCallback(base::Bind( + [](std::unique_ptr context, + const base::Closure& cancel_callback) { + if (cancel_callback) + cancel_callback.Run(); + }, + base::Passed(&context), cancel_callback)); + return reinterpret_cast(raw_context); + } + + private: + class NotificationContext { + public: + explicit NotificationContext(const ContextCallback& callback) + : callback_(callback) {} + + ~NotificationContext() {} + + void SetCancelCallback(const base::Closure& cancel_callback) { + cancel_callback_ = cancel_callback; + } + + void Notify(MojoResult result, MojoHandleSignalsState state) { + if (result == MOJO_RESULT_CANCELLED) + cancel_callback_.Run(); + else + callback_.Run(result, state); + } + + private: + const ContextCallback callback_; + base::Closure cancel_callback_; + + DISALLOW_COPY_AND_ASSIGN(NotificationContext); + }; + + static void Notify(uintptr_t context, + MojoResult result, + MojoHandleSignalsState state, + MojoWatcherNotificationFlags flags) { + reinterpret_cast(context)->Notify(result, state); + } + + DISALLOW_COPY_AND_ASSIGN(WatchHelper); +}; + +class ThreadedRunner : public base::SimpleThread { + public: + explicit ThreadedRunner(const base::Closure& callback) + : SimpleThread("ThreadedRunner"), callback_(callback) {} + ~ThreadedRunner() override {} + + void Run() override { callback_.Run(); } + + private: + const base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(ThreadedRunner); +}; + +void ExpectNoNotification(uintptr_t context, + MojoResult result, + MojoHandleSignalsState state, + MojoWatcherNotificationFlags flags) { + NOTREACHED(); +} + +void ExpectOnlyCancel(uintptr_t context, + MojoResult result, + MojoHandleSignalsState state, + MojoWatcherNotificationFlags flags) { + EXPECT_EQ(result, MOJO_RESULT_CANCELLED); +} + +TEST_F(WatcherTest, InvalidArguments) { + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoCreateWatcher(&ExpectNoNotification, nullptr)); + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w)); + + // Try to watch unwatchable handles. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWatch(w, w, MOJO_HANDLE_SIGNAL_READABLE, 0)); + MojoHandle buffer_handle = CreateBuffer(42); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWatch(w, buffer_handle, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + // Try to cancel a watch on an invalid watcher handle. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoCancelWatch(buffer_handle, 0)); + + // Try to arm an invalid handle. + EXPECT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(MOJO_HANDLE_INVALID, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(buffer_handle, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(buffer_handle)); + + // Try to arm with a non-null count but at least one null output buffer. + uint32_t num_ready_contexts = 1; + uintptr_t ready_context; + MojoResult ready_result; + MojoHandleSignalsState ready_state; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(w, &num_ready_contexts, nullptr, &ready_result, + &ready_state)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(w, &num_ready_contexts, &ready_context, nullptr, + &ready_state)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(w, &num_ready_contexts, &ready_context, + &ready_result, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, WatchMessagePipeReadable) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_expected_notifications = 1; + const uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* expected_count, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_expected_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "hey hey hey hey"; + const char kMessage2[] = "i said hey"; + const char kMessage3[] = "what's goin' on?"; + + // Writing to |b| multiple times should notify exactly once. + WriteMessage(b, kMessage1); + WriteMessage(b, kMessage2); + event.Wait(); + + // This also shouldn't fire a notification; the watcher is still disarmed. + WriteMessage(b, kMessage3); + + // Arming should fail with relevant information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_a_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + // Flush the three messages from above. + EXPECT_EQ(kMessage1, ReadMessage(a)); + EXPECT_EQ(kMessage2, ReadMessage(a)); + EXPECT_EQ(kMessage3, ReadMessage(a)); + + // Now we can rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); +} + +TEST_F(WatcherTest, CloseWatchedMessagePipeHandle) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_a_context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + + // Test that closing a watched handle fires an appropriate notification, even + // when the watcher is unarmed. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatchedMessagePipeHandlePeer) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + event->Signal(); + }, + &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + + // Test that closing a watched handle's peer with an armed watcher fires an + // appropriate notification. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + event.Wait(); + + // And now arming should fail with correct information about |a|'s state. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_a_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_READABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); +} + +TEST_F(WatcherTest, WatchDataPipeConsumerReadable) { + constexpr size_t kTestPipeCapacity = 64; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_expected_notifications = 1; + const uintptr_t readable_consumer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* expected_count, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_expected_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE, + readable_consumer_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "hey hey hey hey"; + const char kMessage2[] = "i said hey"; + const char kMessage3[] = "what's goin' on?"; + + // Writing to |producer| multiple times should notify exactly once. + WriteData(producer, kMessage1); + WriteData(producer, kMessage2); + event.Wait(); + + // This also shouldn't fire a notification; the watcher is still disarmed. + WriteData(producer, kMessage3); + + // Arming should fail with relevant information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_consumer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + // Flush the three messages from above. + EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1)); + EXPECT_EQ(kMessage2, ReadData(consumer, sizeof(kMessage2) - 1)); + EXPECT_EQ(kMessage3, ReadData(consumer, sizeof(kMessage3) - 1)); + + // Now we can rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +} + +TEST_F(WatcherTest, WatchDataPipeConsumerNewDataReadable) { + constexpr size_t kTestPipeCapacity = 64; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_new_data_notifications = 0; + const uintptr_t new_data_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* notification_count, MojoResult result, + MojoHandleSignalsState state) { + *notification_count += 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_new_data_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + new_data_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "hey hey hey hey"; + const char kMessage2[] = "i said hey"; + const char kMessage3[] = "what's goin' on?"; + + // Writing to |producer| multiple times should notify exactly once. + WriteData(producer, kMessage1); + WriteData(producer, kMessage2); + event.Wait(); + + // This also shouldn't fire a notification; the watcher is still disarmed. + WriteData(producer, kMessage3); + + // Arming should fail with relevant information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(new_data_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + // Attempt to read more data than is available. Should fail but clear the + // NEW_DATA_READABLE signal. + char large_buffer[512]; + uint32_t large_read_size = 512; + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + MojoReadData(consumer, large_buffer, &large_read_size, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // Attempt to arm again. Should succeed. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Write more data. Should notify. + event.Reset(); + WriteData(producer, kMessage1); + event.Wait(); + + // Reading some data should clear NEW_DATA_READABLE again so we can rearm. + EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(2, num_new_data_notifications); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +} + +TEST_F(WatcherTest, WatchDataPipeProducerWritable) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + // Half the capacity of the data pipe. + const char kTestData[] = "aaaa"; + static_assert((sizeof(kTestData) - 1) * 2 == kTestPipeCapacity, + "Invalid test data for this test."); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_expected_notifications = 1; + const uintptr_t writable_producer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* expected_count, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_expected_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE, + writable_producer_context)); + + // The producer is already writable, so arming should fail with relevant + // information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Write some data, but don't fill the pipe yet. Arming should fail again. + WriteData(producer, kTestData); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Write more data, filling the pipe to capacity. Arming should succeed now. + WriteData(producer, kTestData); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Now read from the pipe, making the producer writable again. Should notify. + EXPECT_EQ(kTestData, ReadData(consumer, sizeof(kTestData) - 1)); + event.Wait(); + + // Arming should fail again. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Fill the pipe once more and arm the watcher. Should succeed. + WriteData(producer, kTestData); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +}; + +TEST_F(WatcherTest, CloseWatchedDataPipeConsumerHandle) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_consumer_context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE, + readable_consumer_context)); + + // Closing the consumer should fire a cancellation notification. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatcherDataPipeConsumerHandlePeer) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_consumer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + event->Signal(); + }, + &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE, + readable_consumer_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Closing the producer should fire a notification for an unsatisfiable watch. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + event.Wait(); + + // Now attempt to rearm and expect appropriate error feedback. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_consumer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_READABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +} + +TEST_F(WatcherTest, CloseWatchedDataPipeProducerHandle) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t writable_producer_context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE, + writable_producer_context)); + + // Closing the consumer should fire a cancellation notification. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatchedDataPipeProducerHandlePeer) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + const char kTestMessageFullCapacity[] = "xxxxxxxx"; + static_assert(sizeof(kTestMessageFullCapacity) - 1 == kTestPipeCapacity, + "Invalid test message size for this test."); + + // Make the pipe unwritable initially. + WriteData(producer, kTestMessageFullCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t writable_producer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + event->Signal(); + }, + &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE, + writable_producer_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Closing the consumer should fire a notification for an unsatisfiable watch, + // as the full data pipe can never be read from again and is therefore + // permanently full and unwritable. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); + event.Wait(); + + // Now attempt to rearm and expect appropriate error feedback. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_WRITABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); +} + +TEST_F(WatcherTest, ArmWithNoWatches) { + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w)); + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, WatchDuplicateContext) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, 0)); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, CancelUnknownWatch) { + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w)); + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, MojoCancelWatch(w, 1234)); +} + +TEST_F(WatcherTest, ArmWithWatchAlreadySatisfied) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_WRITABLE, 0)); + + // |a| is always writable, so we can never arm this watcher. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(0u, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, ArmWithWatchAlreadyUnsatisfiable) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + + // |b| is closed and never wrote any messages, so |a| won't be readable again. + // MojoArmWatcher() should fail, incidcating as much. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(0u, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_READABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); +} + +TEST_F(WatcherTest, MultipleWatches) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent a_event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent b_event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_a_notifications = 0; + int num_b_notifications = 0; + auto notify_callback = + base::Bind([](base::WaitableEvent* event, int* notification_count, + MojoResult result, MojoHandleSignalsState state) { + *notification_count += 1; + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }); + uintptr_t readable_a_context = helper.CreateContext( + base::Bind(notify_callback, &a_event, &num_a_notifications)); + uintptr_t readable_b_context = helper.CreateContext( + base::Bind(notify_callback, &b_event, &num_b_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + // Add two independent watch contexts to watch for |a| or |b| readability. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, readable_b_context)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "things are happening"; + const char kMessage2[] = "ok. ok. ok. ok."; + const char kMessage3[] = "plz wake up"; + + // Writing to |b| should signal |a|'s watch. + WriteMessage(b, kMessage1); + a_event.Wait(); + a_event.Reset(); + + // Subsequent messages on |b| should not trigger another notification. + WriteMessage(b, kMessage2); + WriteMessage(b, kMessage3); + + // Messages on |a| also shouldn't trigger |b|'s notification, since the + // watcher should be disarmed by now. + WriteMessage(a, kMessage1); + WriteMessage(a, kMessage2); + WriteMessage(a, kMessage3); + + // Arming should fail. Since we only ask for at most one context's information + // that's all we should get back. Which one we get is unspecified. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = 1; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_TRUE(ready_contexts[0] == readable_a_context || + ready_contexts[0] == readable_b_context); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Now try arming again, verifying that both contexts are returned. + num_ready_contexts = kMaxReadyContexts; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(2u, num_ready_contexts); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[1]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + EXPECT_TRUE(ready_states[1].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + EXPECT_TRUE((ready_contexts[0] == readable_a_context && + ready_contexts[1] == readable_b_context) || + (ready_contexts[0] == readable_b_context && + ready_contexts[1] == readable_a_context)); + + // Flush out the test messages so we should be able to successfully rearm. + EXPECT_EQ(kMessage1, ReadMessage(a)); + EXPECT_EQ(kMessage2, ReadMessage(a)); + EXPECT_EQ(kMessage3, ReadMessage(a)); + EXPECT_EQ(kMessage1, ReadMessage(b)); + EXPECT_EQ(kMessage2, ReadMessage(b)); + EXPECT_EQ(kMessage3, ReadMessage(b)); + + // Add a watch which is always satisfied, so we can't arm. Arming should fail + // with only this new watch's information. + uintptr_t writable_c_context = helper.CreateContext(base::Bind( + [](MojoResult result, MojoHandleSignalsState state) { NOTREACHED(); })); + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, c, MOJO_HANDLE_SIGNAL_WRITABLE, writable_c_context)); + num_ready_contexts = kMaxReadyContexts; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_c_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Cancel the new watch and arming should succeed once again. + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, writable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, NotifyOtherFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hello a"; + static const char kTestMessageToB[] = "hello b"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](MojoHandle w, MojoHandle a, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ("hello a", ReadMessage(a)); + + // Re-arm the watcher and signal |b|. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + WriteMessage(a, kTestMessageToB); + }, + w, a)); + + uintptr_t readable_b_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle b, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToB, ReadMessage(b)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + event->Signal(); + }, + &event, w, b)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Send a message to |a|. The relevant watch context should be notified, and + // should in turn send a message to |b|, waking up the other context. The + // second context signals |event|. + WriteMessage(b, kTestMessageToA); + event.Wait(); +} + +TEST_F(WatcherTest, NotifySelfFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hello a"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + int expected_notifications = 10; + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](int* expected_count, MojoHandle w, MojoHandle a, MojoHandle b, + base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ("hello a", ReadMessage(a)); + + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + if (*expected_count == 0) { + event->Signal(); + return; + } else { + // Re-arm the watcher and signal |a| again. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + WriteMessage(b, kTestMessageToA); + } + }, + &expected_notifications, w, a, b, &event)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Send a message to |a|. When the watch above is notified, it will rearm and + // send another message to |a|. This will happen until + // |expected_notifications| reaches 0. + WriteMessage(b, kTestMessageToA); + event.Wait(); +} + +TEST_F(WatcherTest, ImplicitCancelOtherFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + static const char kTestMessageToA[] = "hi a"; + static const char kTestMessageToC[] = "hi c"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + uintptr_t readable_a_context = helper.CreateContextWithCancel( + base::Bind([](MojoResult result, MojoHandleSignalsState state) { + NOTREACHED(); + }), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + uintptr_t readable_c_context = helper.CreateContext(base::Bind( + [](MojoHandle w, MojoHandle a, MojoHandle b, MojoHandle c, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToC, ReadMessage(c)); + + // Now rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Must result in exactly ONE notification on the above context, for + // CANCELLED only. Because we cannot dispatch notifications until the + // stack unwinds, and because we must never dispatch non-cancellation + // notifications for a handle once it's been closed, we must be certain + // that cancellation due to closure preemptively invalidates any + // pending non-cancellation notifications queued on the current + // RequestContext, such as the one resulting from the WriteMessage here. + WriteMessage(b, kTestMessageToA); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + // Rearming should be fine since |a|'s watch should already be + // implicitly cancelled (even though the notification will not have + // been invoked yet.) + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Nothing interesting should happen as a result of this. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + }, + w, a, b, c)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, c, MOJO_HANDLE_SIGNAL_READABLE, readable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(d, kTestMessageToC); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, ExplicitCancelOtherFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + static const char kTestMessageToA[] = "hi a"; + static const char kTestMessageToC[] = "hi c"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](MojoResult result, MojoHandleSignalsState state) { NOTREACHED(); })); + + uintptr_t readable_c_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, uintptr_t readable_a_context, MojoHandle w, + MojoHandle a, MojoHandle b, MojoHandle c, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToC, ReadMessage(c)); + + // Now rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Should result in no notifications on the above context, because the + // watch will have been cancelled by the time the notification callback + // can execute. + WriteMessage(b, kTestMessageToA); + WriteMessage(b, kTestMessageToA); + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context)); + + // Rearming should be fine now. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Nothing interesting should happen as a result of these. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + + event->Signal(); + }, + &event, readable_a_context, w, a, b, c)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, c, MOJO_HANDLE_SIGNAL_READABLE, readable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(d, kTestMessageToC); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, NestedCancellation) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + static const char kTestMessageToA[] = "hey a"; + static const char kTestMessageToC[] = "hey c"; + static const char kTestMessageToD[] = "hey d"; + + // This is a tricky test. It establishes a watch on |b| using one watcher and + // watches on |c| and |d| using another watcher. + // + // A message is written to |d| to wake up |c|'s watch, and the notification + // handler for that event does the following: + // 1. Writes to |a| to eventually wake up |b|'s watcher. + // 2. Rearms |c|'s watcher. + // 3. Writes to |d| to eventually wake up |c|'s watcher again. + // + // Meanwhile, |b|'s watch notification handler cancels |c|'s watch altogether + // before writing to |c| to wake up |d|. + // + // The net result should be that |c|'s context only gets notified once (from + // the first write to |d| above) and everyone else gets notified as expected. + + MojoHandle b_watcher; + MojoHandle cd_watcher; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&b_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&cd_watcher)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + uintptr_t readable_d_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle d, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToD, ReadMessage(d)); + event->Signal(); + }, + &event, d)); + + static int num_expected_c_notifications = 1; + uintptr_t readable_c_context = helper.CreateContext(base::Bind( + [](MojoHandle cd_watcher, MojoHandle a, MojoHandle c, MojoHandle d, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_GT(num_expected_c_notifications--, 0); + + // Trigger an eventual |readable_b_context| notification. + WriteMessage(a, kTestMessageToA); + + EXPECT_EQ(kTestMessageToC, ReadMessage(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoArmWatcher(cd_watcher, nullptr, nullptr, + nullptr, nullptr)); + + // Trigger another eventual |readable_c_context| notification. + WriteMessage(d, kTestMessageToC); + }, + cd_watcher, a, c, d)); + + uintptr_t readable_b_context = helper.CreateContext(base::Bind( + [](MojoHandle cd_watcher, uintptr_t readable_c_context, MojoHandle c, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, + MojoCancelWatch(cd_watcher, readable_c_context)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoArmWatcher(cd_watcher, nullptr, nullptr, + nullptr, nullptr)); + + WriteMessage(c, kTestMessageToD); + }, + cd_watcher, readable_c_context, c)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE, + readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(cd_watcher, c, MOJO_HANDLE_SIGNAL_READABLE, + readable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(cd_watcher, d, MOJO_HANDLE_SIGNAL_READABLE, + readable_d_context)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(b_watcher, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(cd_watcher, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(d, kTestMessageToC); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(cd_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, CancelSelfInNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + static uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle a, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + + // There should be no problem cancelling this watch from its own + // notification invocation. + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context)); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + + // Arming should fail because there are no longer any registered + // watches on the watcher. + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // And closing |a| should be fine (and should not invoke this + // notification with MOJO_RESULT_CANCELLED) for the same reason. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + event->Signal(); + }, + &event, w, a)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatcherInNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA1[] = "hey a"; + static const char kTestMessageToA2[] = "hey a again"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle a, MojoHandle b, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA1, ReadMessage(a)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // There should be no problem closing this watcher from its own + // notification callback. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + + // And these should not trigger more notifications, because |w| has been + // closed already. + WriteMessage(b, kTestMessageToA2); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + event->Signal(); + }, + &event, w, a, b)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA1); + event.Wait(); +} + +TEST_F(WatcherTest, CloseWatcherAfterImplicitCancel) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle a, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // This will cue up a notification for |MOJO_RESULT_CANCELLED|... + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + // ...but it should never fire because we close the watcher here. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + + event->Signal(); + }, + &event, w, a)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, OtherThreadCancelDuringNotification) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent wait_for_notification( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + base::WaitableEvent wait_for_cancellation( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + static bool callback_done = false; + uintptr_t readable_a_context = helper.CreateContextWithCancel( + base::Bind( + [](base::WaitableEvent* wait_for_notification, MojoHandle w, + MojoHandle a, MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + + wait_for_notification->Signal(); + + // Give the other thread sufficient time to race with the completion + // of this callback. There should be no race, since the cancellation + // notification must be mutually exclusive to this notification. + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); + + callback_done = true; + }, + &wait_for_notification, w, a), + base::Bind( + [](base::WaitableEvent* wait_for_cancellation) { + EXPECT_TRUE(callback_done); + wait_for_cancellation->Signal(); + }, + &wait_for_cancellation)); + + ThreadedRunner runner(base::Bind( + [](base::WaitableEvent* wait_for_notification, + base::WaitableEvent* wait_for_cancellation, MojoHandle w, + uintptr_t readable_a_context) { + wait_for_notification->Wait(); + + // Cancel the watch while the notification is still running. + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context)); + + wait_for_cancellation->Wait(); + + EXPECT_TRUE(callback_done); + }, + &wait_for_notification, &wait_for_cancellation, w, readable_a_context)); + runner.Start(); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA); + runner.Join(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, WatchesCancelEachOtherFromNotifications) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + static const char kTestMessageToB[] = "hey b"; + + base::WaitableEvent wait_for_a_to_notify( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent wait_for_b_to_notify( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent wait_for_a_to_cancel( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent wait_for_b_to_cancel( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + MojoHandle a_watcher; + MojoHandle b_watcher; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&a_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&b_watcher)); + + // We set up two watchers, one on |a| and one on |b|. They cancel each other + // from within their respective watch notifications. This should be safe, + // i.e., it should not deadlock, in spite of the fact that we also guarantee + // mutually exclusive notification execution (including cancellations) on any + // given watch. + bool a_cancelled = false; + bool b_cancelled = false; + static uintptr_t readable_b_context; + uintptr_t readable_a_context = helper.CreateContextWithCancel( + base::Bind( + [](base::WaitableEvent* wait_for_a_to_notify, + base::WaitableEvent* wait_for_b_to_notify, MojoHandle b_watcher, + MojoHandle a, MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + wait_for_a_to_notify->Signal(); + wait_for_b_to_notify->Wait(); + EXPECT_EQ(MOJO_RESULT_OK, + MojoCancelWatch(b_watcher, readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_watcher)); + }, + &wait_for_a_to_notify, &wait_for_b_to_notify, b_watcher, a), + base::Bind( + [](base::WaitableEvent* wait_for_a_to_cancel, + base::WaitableEvent* wait_for_b_to_cancel, bool* a_cancelled) { + *a_cancelled = true; + wait_for_a_to_cancel->Signal(); + wait_for_b_to_cancel->Wait(); + }, + &wait_for_a_to_cancel, &wait_for_b_to_cancel, &a_cancelled)); + + readable_b_context = helper.CreateContextWithCancel( + base::Bind( + [](base::WaitableEvent* wait_for_a_to_notify, + base::WaitableEvent* wait_for_b_to_notify, + uintptr_t readable_a_context, MojoHandle a_watcher, MojoHandle b, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToB, ReadMessage(b)); + wait_for_b_to_notify->Signal(); + wait_for_a_to_notify->Wait(); + EXPECT_EQ(MOJO_RESULT_OK, + MojoCancelWatch(a_watcher, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a_watcher)); + }, + &wait_for_a_to_notify, &wait_for_b_to_notify, readable_a_context, + a_watcher, b), + base::Bind( + [](base::WaitableEvent* wait_for_a_to_cancel, + base::WaitableEvent* wait_for_b_to_cancel, bool* b_cancelled) { + *b_cancelled = true; + wait_for_b_to_cancel->Signal(); + wait_for_a_to_cancel->Wait(); + }, + &wait_for_a_to_cancel, &wait_for_b_to_cancel, &b_cancelled)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(a_watcher, a, MOJO_HANDLE_SIGNAL_READABLE, + readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(a_watcher, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE, + readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(b_watcher, nullptr, nullptr, nullptr, nullptr)); + + ThreadedRunner runner( + base::Bind([](MojoHandle b) { WriteMessage(b, kTestMessageToA); }, b)); + runner.Start(); + + WriteMessage(a, kTestMessageToB); + + wait_for_a_to_cancel.Wait(); + wait_for_b_to_cancel.Wait(); + runner.Join(); + + EXPECT_TRUE(a_cancelled); + EXPECT_TRUE(b_cancelled); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, AlwaysCancel) { + // Basic sanity check to ensure that all possible ways to cancel a watch + // result in a final MOJO_RESULT_CANCELLED notification. + + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + const base::Closure signal_event = + base::Bind(&base::WaitableEvent::Signal, base::Unretained(&event)); + + // Cancel via |MojoCancelWatch()|. + uintptr_t context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), signal_event); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, context)); + event.Wait(); + event.Reset(); + + // Cancel by closing the watched handle. + context = helper.CreateContextWithCancel(WatchHelper::ContextCallback(), + signal_event); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + event.Wait(); + event.Reset(); + + // Cancel by closing the watcher handle. + context = helper.CreateContextWithCancel(WatchHelper::ContextCallback(), + signal_event); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, ArmFailureCirculation) { + // Sanity check to ensure that all ready handles will eventually be returned + // over a finite number of calls to MojoArmWatcher(). + + constexpr size_t kNumTestPipes = 100; + constexpr size_t kNumTestHandles = kNumTestPipes * 2; + MojoHandle handles[kNumTestHandles]; + + // Create a bunch of pipes and make sure they're all readable. + for (size_t i = 0; i < kNumTestPipes; ++i) { + CreateMessagePipe(&handles[i], &handles[i + kNumTestPipes]); + WriteMessage(handles[i], "hey"); + WriteMessage(handles[i + kNumTestPipes], "hay"); + WaitForSignals(handles[i], MOJO_HANDLE_SIGNAL_READABLE); + WaitForSignals(handles[i + kNumTestPipes], MOJO_HANDLE_SIGNAL_READABLE); + } + + // Create a watcher and watch all of them. + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + for (size_t i = 0; i < kNumTestHandles; ++i) { + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, handles[i], MOJO_HANDLE_SIGNAL_READABLE, i)); + } + + // Keep trying to arm |w| until every watch gets an entry in |ready_contexts|. + // If MojoArmWatcher() is well-behaved, this should terminate eventually. + std::set ready_contexts; + while (ready_contexts.size() < kNumTestHandles) { + uint32_t num_ready_contexts = 1; + uintptr_t ready_context; + MojoResult ready_result; + MojoHandleSignalsState ready_state; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, &ready_context, + &ready_result, &ready_state)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(MOJO_RESULT_OK, ready_result); + ready_contexts.insert(ready_context); + } + + for (size_t i = 0; i < kNumTestHandles; ++i) + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[i])); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/BUILD.gn b/mojo/edk/test/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..a15456a32f6586dc913564a12b1cc273be9da4e6 --- /dev/null +++ b/mojo/edk/test/BUILD.gn @@ -0,0 +1,131 @@ +# Copyright 2014 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("//testing/test.gni") + +static_library("test_support") { + testonly = true + sources = [ + "mojo_test_base.cc", + "mojo_test_base.h", + "test_utils.h", + "test_utils_posix.cc", + "test_utils_win.cc", + ] + + if (!is_ios) { + sources += [ + "multiprocess_test_helper.cc", + "multiprocess_test_helper.h", + ] + } + + deps = [ + "//base", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/public/cpp/system", + "//testing/gtest", + ] +} + +source_set("run_all_unittests") { + testonly = true + sources = [ + "run_all_unittests.cc", + ] + + deps = [ + ":test_support", + ":test_support_impl", + "//base", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/public/c/test_support", + "//testing/gtest", + ] + + if (is_linux && !is_component_build) { + public_configs = [ "//build/config/gcc:rpath_for_built_shared_libraries" ] + } +} + +source_set("run_all_perftests") { + testonly = true + deps = [ + ":test_support_impl", + "//base", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/edk/test:test_support", + "//mojo/public/c/test_support", + ] + + sources = [ + "run_all_perftests.cc", + ] + + if (is_linux && !is_component_build) { + public_configs = [ "//build/config/gcc:rpath_for_built_shared_libraries" ] + } +} + +static_library("test_support_impl") { + testonly = true + deps = [ + "//base", + "//base/test:test_support", + "//mojo/public/c/test_support", + "//mojo/public/cpp/system", + ] + + sources = [ + "test_support_impl.cc", + "test_support_impl.h", + ] +} + +# Public SDK test targets follow. These targets are not defined within the +# public SDK itself as running the unittests requires the EDK. +# TODO(vtl): These don't really belong here. (They should be converted to +# apptests, but even apart from that these targets belong somewhere else.) + +group("public_tests") { + testonly = true + deps = [ + ":mojo_public_bindings_unittests", + ":mojo_public_system_perftests", + ":mojo_public_system_unittests", + ] +} + +test("mojo_public_bindings_perftests") { + deps = [ + ":run_all_perftests", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/bindings/tests:perftests", + ] +} + +test("mojo_public_bindings_unittests") { + deps = [ + ":run_all_unittests", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/bindings/tests", + ] +} + +test("mojo_public_system_perftests") { + deps = [ + ":run_all_perftests", + "//mojo/public/c/system/tests:perftests", + ] +} + +test("mojo_public_system_unittests") { + deps = [ + ":run_all_unittests", + "//mojo/public/cpp/system/tests", + ] +} diff --git a/mojo/edk/test/mojo_test_base.cc b/mojo/edk/test/mojo_test_base.cc new file mode 100644 index 0000000000000000000000000000000000000000..71a5e3b9a59c745eae3dfbc107bc8af6bdb65cd1 --- /dev/null +++ b/mojo/edk/test/mojo_test_base.cc @@ -0,0 +1,327 @@ +// Copyright 2016 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. + +#include "mojo/edk/test/mojo_test_base.h" + +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/watcher.h" +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "base/mac/mach_port_broker.h" +#endif + +namespace mojo { +namespace edk { +namespace test { + +#if defined(OS_MACOSX) && !defined(OS_IOS) +namespace { +base::MachPortBroker* g_mach_broker = nullptr; +} +#endif + +MojoTestBase::MojoTestBase() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + if (!g_mach_broker) { + g_mach_broker = new base::MachPortBroker("mojo_test"); + CHECK(g_mach_broker->Init()); + SetMachPortProvider(g_mach_broker); + } +#endif +} + +MojoTestBase::~MojoTestBase() {} + +MojoTestBase::ClientController& MojoTestBase::StartClient( + const std::string& client_name) { + clients_.push_back(base::MakeUnique( + client_name, this, process_error_callback_, launch_type_)); + return *clients_.back(); +} + +MojoTestBase::ClientController::ClientController( + const std::string& client_name, + MojoTestBase* test, + const ProcessErrorCallback& process_error_callback, + LaunchType launch_type) { +#if !defined(OS_IOS) +#if defined(OS_MACOSX) + // This lock needs to be held while launching the child because the Mach port + // broker only allows task ports to be received from known child processes. + // However, it can only know the child process's pid after the child has + // launched. To prevent a race where the child process sends its task port + // before the pid has been registered, the lock needs to be held over both + // launch and child pid registration. + base::AutoLock lock(g_mach_broker->GetLock()); +#endif + helper_.set_process_error_callback(process_error_callback); + pipe_ = helper_.StartChild(client_name, launch_type); +#if defined(OS_MACOSX) + g_mach_broker->AddPlaceholderForPid(helper_.test_child().Handle()); +#endif +#endif +} + +MojoTestBase::ClientController::~ClientController() { + CHECK(was_shutdown_) + << "Test clients should be waited on explicitly with WaitForShutdown()."; +} + +void MojoTestBase::ClientController::ClosePeerConnection() { +#if !defined(OS_IOS) + helper_.ClosePeerConnection(); +#endif +} + +int MojoTestBase::ClientController::WaitForShutdown() { + was_shutdown_ = true; +#if !defined(OS_IOS) + int retval = helper_.WaitForChildShutdown(); +#if defined(OS_MACOSX) + base::AutoLock lock(g_mach_broker->GetLock()); + g_mach_broker->InvalidatePid(helper_.test_child().Handle()); +#endif + return retval; +#else + NOTREACHED(); + return 1; +#endif +} + +// static +void MojoTestBase::CloseHandle(MojoHandle h) { + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h)); +} + +// static +void MojoTestBase::CreateMessagePipe(MojoHandle *p0, MojoHandle* p1) { + MojoCreateMessagePipe(nullptr, p0, p1); + CHECK_NE(*p0, MOJO_HANDLE_INVALID); + CHECK_NE(*p1, MOJO_HANDLE_INVALID); +} + +// static +void MojoTestBase::WriteMessageWithHandles(MojoHandle mp, + const std::string& message, + const MojoHandle *handles, + uint32_t num_handles) { + CHECK_EQ(MojoWriteMessage(mp, message.data(), + static_cast(message.size()), + handles, num_handles, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); +} + +// static +void MojoTestBase::WriteMessage(MojoHandle mp, const std::string& message) { + WriteMessageWithHandles(mp, message, nullptr, 0); +} + +// static +std::string MojoTestBase::ReadMessageWithHandles( + MojoHandle mp, + MojoHandle* handles, + uint32_t expected_num_handles) { + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); + + uint32_t message_size = 0; + uint32_t num_handles = 0; + CHECK_EQ(MojoReadMessage(mp, nullptr, &message_size, nullptr, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_RESOURCE_EXHAUSTED); + CHECK_EQ(expected_num_handles, num_handles); + + std::string message(message_size, 'x'); + CHECK_EQ(MojoReadMessage(mp, &message[0], &message_size, handles, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(message_size, message.size()); + CHECK_EQ(num_handles, expected_num_handles); + + return message; +} + +// static +std::string MojoTestBase::ReadMessageWithOptionalHandle(MojoHandle mp, + MojoHandle* handle) { + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); + + uint32_t message_size = 0; + uint32_t num_handles = 0; + CHECK_EQ(MojoReadMessage(mp, nullptr, &message_size, nullptr, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_RESOURCE_EXHAUSTED); + CHECK(num_handles == 0 || num_handles == 1); + + CHECK(handle); + + std::string message(message_size, 'x'); + CHECK_EQ(MojoReadMessage(mp, &message[0], &message_size, handle, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(message_size, message.size()); + CHECK(num_handles == 0 || num_handles == 1); + + if (num_handles) + CHECK_NE(*handle, MOJO_HANDLE_INVALID); + else + *handle = MOJO_HANDLE_INVALID; + + return message; +} + +// static +std::string MojoTestBase::ReadMessage(MojoHandle mp) { + return ReadMessageWithHandles(mp, nullptr, 0); +} + +// static +void MojoTestBase::ReadMessage(MojoHandle mp, + char* data, + size_t num_bytes) { + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); + + uint32_t message_size = 0; + uint32_t num_handles = 0; + CHECK_EQ(MojoReadMessage(mp, nullptr, &message_size, nullptr, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_RESOURCE_EXHAUSTED); + CHECK_EQ(num_handles, 0u); + CHECK_EQ(message_size, num_bytes); + + CHECK_EQ(MojoReadMessage(mp, data, &message_size, nullptr, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(num_handles, 0u); + CHECK_EQ(message_size, num_bytes); +} + +// static +void MojoTestBase::VerifyTransmission(MojoHandle source, + MojoHandle dest, + const std::string& message) { + WriteMessage(source, message); + + // We don't use EXPECT_EQ; failures on really long messages make life hard. + EXPECT_TRUE(message == ReadMessage(dest)); +} + +// static +void MojoTestBase::VerifyEcho(MojoHandle mp, + const std::string& message) { + VerifyTransmission(mp, mp, message); +} + +// static +MojoHandle MojoTestBase::CreateBuffer(uint64_t size) { + MojoHandle h; + EXPECT_EQ(MojoCreateSharedBuffer(nullptr, size, &h), MOJO_RESULT_OK); + return h; +} + +// static +MojoHandle MojoTestBase::DuplicateBuffer(MojoHandle h, bool read_only) { + MojoHandle new_handle; + MojoDuplicateBufferHandleOptions options = { + sizeof(MojoDuplicateBufferHandleOptions), + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE + }; + if (read_only) + options.flags |= MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY; + EXPECT_EQ(MOJO_RESULT_OK, + MojoDuplicateBufferHandle(h, &options, &new_handle)); + return new_handle; +} + +// static +void MojoTestBase::WriteToBuffer(MojoHandle h, + size_t offset, + const base::StringPiece& s) { + char* data; + EXPECT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(h, offset, s.size(), reinterpret_cast(&data), + MOJO_MAP_BUFFER_FLAG_NONE)); + memcpy(data, s.data(), s.size()); + EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(static_cast(data))); +} + +// static +void MojoTestBase::ExpectBufferContents(MojoHandle h, + size_t offset, + const base::StringPiece& s) { + char* data; + EXPECT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(h, offset, s.size(), reinterpret_cast(&data), + MOJO_MAP_BUFFER_FLAG_NONE)); + EXPECT_EQ(s, base::StringPiece(data, s.size())); + EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(static_cast(data))); +} + +// static +void MojoTestBase::CreateDataPipe(MojoHandle *p0, + MojoHandle* p1, + size_t capacity) { + MojoCreateDataPipeOptions options; + options.struct_size = static_cast(sizeof(options)); + options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE; + options.element_num_bytes = 1; + options.capacity_num_bytes = static_cast(capacity); + + MojoCreateDataPipe(&options, p0, p1); + CHECK_NE(*p0, MOJO_HANDLE_INVALID); + CHECK_NE(*p1, MOJO_HANDLE_INVALID); +} + +// static +void MojoTestBase::WriteData(MojoHandle producer, const std::string& data) { + CHECK_EQ(WaitForSignals(producer, MOJO_HANDLE_SIGNAL_WRITABLE), + MOJO_RESULT_OK); + uint32_t num_bytes = static_cast(data.size()); + CHECK_EQ(MojoWriteData(producer, data.data(), &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE), + MOJO_RESULT_OK); + CHECK_EQ(num_bytes, static_cast(data.size())); +} + +// static +std::string MojoTestBase::ReadData(MojoHandle consumer, size_t size) { + CHECK_EQ(WaitForSignals(consumer, MOJO_HANDLE_SIGNAL_READABLE), + MOJO_RESULT_OK); + std::vector buffer(size); + uint32_t num_bytes = static_cast(size); + CHECK_EQ(MojoReadData(consumer, buffer.data(), &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE), + MOJO_RESULT_OK); + CHECK_EQ(num_bytes, static_cast(size)); + + return std::string(buffer.data(), buffer.size()); +} + +// static +MojoHandleSignalsState MojoTestBase::GetSignalsState(MojoHandle handle) { + MojoHandleSignalsState signals_state; + CHECK_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(handle, &signals_state)); + return signals_state; +} + +// static +MojoResult MojoTestBase::WaitForSignals(MojoHandle handle, + MojoHandleSignals signals, + MojoHandleSignalsState* state) { + return Wait(Handle(handle), signals, state); +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/mojo_test_base.h b/mojo/edk/test/mojo_test_base.h new file mode 100644 index 0000000000000000000000000000000000000000..35e2c2b12f9986b85a99daade269382eb713789c --- /dev/null +++ b/mojo/edk/test/mojo_test_base.h @@ -0,0 +1,249 @@ +// Copyright 2016 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 MOJO_EDK_TEST_MOJO_TEST_BASE_H_ +#define MOJO_EDK_TEST_MOJO_TEST_BASE_H_ + +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/test/multiprocess_test_helper.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace test { + +class MojoTestBase : public testing::Test { + public: + MojoTestBase(); + ~MojoTestBase() override; + + using LaunchType = MultiprocessTestHelper::LaunchType; + using HandlerCallback = base::Callback; + + class ClientController { + public: + ClientController(const std::string& client_name, + MojoTestBase* test, + const ProcessErrorCallback& process_error_callback, + LaunchType launch_type); + ~ClientController(); + + MojoHandle pipe() const { return pipe_.get().value(); } + + void ClosePeerConnection(); + int WaitForShutdown(); + + private: + friend class MojoTestBase; + +#if !defined(OS_IOS) + MultiprocessTestHelper helper_; +#endif + ScopedMessagePipeHandle pipe_; + bool was_shutdown_ = false; + + DISALLOW_COPY_AND_ASSIGN(ClientController); + }; + + // Set the callback to handle bad messages received from test client + // processes. This can be set to a different callback before starting each + // client. + void set_process_error_callback(const ProcessErrorCallback& callback) { + process_error_callback_ = callback; + } + + ClientController& StartClient(const std::string& client_name); + + template + void StartClientWithHandler(const std::string& client_name, + HandlerFunc handler) { + int expected_exit_code = 0; + ClientController& c = StartClient(client_name); + handler(c.pipe(), &expected_exit_code); + EXPECT_EQ(expected_exit_code, c.WaitForShutdown()); + } + + // Closes a handle and expects success. + static void CloseHandle(MojoHandle h); + + ////// Message pipe test utilities /////// + + // Creates a new pipe, returning endpoint handles in |p0| and |p1|. + static void CreateMessagePipe(MojoHandle* p0, MojoHandle* p1); + + // Writes a string to the pipe, transferring handles in the process. + static void WriteMessageWithHandles(MojoHandle mp, + const std::string& message, + const MojoHandle* handles, + uint32_t num_handles); + + // Writes a string to the pipe with no handles. + static void WriteMessage(MojoHandle mp, const std::string& message); + + // Reads a string from the pipe, expecting to read an exact number of handles + // in the process. Returns the read string. + static std::string ReadMessageWithHandles(MojoHandle mp, + MojoHandle* handles, + uint32_t expected_num_handles); + + // Reads a string from the pipe, expecting either zero or one handles. + // If no handle is read, |handle| will be reset. + static std::string ReadMessageWithOptionalHandle(MojoHandle mp, + MojoHandle* handle); + + // Reads a string from the pipe, expecting to read no handles. + // Returns the string. + static std::string ReadMessage(MojoHandle mp); + + // Reads a string from the pipe, expecting to read no handles and exactly + // |num_bytes| bytes, which are read into |data|. + static void ReadMessage(MojoHandle mp, char* data, size_t num_bytes); + + // Writes |message| to |in| and expects to read it back from |out|. + static void VerifyTransmission(MojoHandle in, + MojoHandle out, + const std::string& message); + + // Writes |message| to |mp| and expects to read it back from the same handle. + static void VerifyEcho(MojoHandle mp, const std::string& message); + + //////// Shared buffer test utilities ///////// + + // Creates a new shared buffer. + static MojoHandle CreateBuffer(uint64_t size); + + // Duplicates a shared buffer to a new handle. + static MojoHandle DuplicateBuffer(MojoHandle h, bool read_only); + + // Maps a buffer, writes some data into it, and unmaps it. + static void WriteToBuffer(MojoHandle h, + size_t offset, + const base::StringPiece& s); + + // Maps a buffer, tests the value of some of its contents, and unmaps it. + static void ExpectBufferContents(MojoHandle h, + size_t offset, + const base::StringPiece& s); + + //////// Data pipe test utilities ///////// + + // Creates a new data pipe. + static void CreateDataPipe(MojoHandle* producer, + MojoHandle* consumer, + size_t capacity); + + // Writes data to a data pipe. + static void WriteData(MojoHandle producer, const std::string& data); + + // Reads data from a data pipe. + static std::string ReadData(MojoHandle consumer, size_t size); + + // Queries the signals state of |handle|. + static MojoHandleSignalsState GetSignalsState(MojoHandle handle); + + // Helper to block the calling thread waiting for signals to be raised. + static MojoResult WaitForSignals(MojoHandle handle, + MojoHandleSignals signals, + MojoHandleSignalsState* state = nullptr); + + void set_launch_type(LaunchType launch_type) { launch_type_ = launch_type; } + + private: + friend class ClientController; + + std::vector> clients_; + + ProcessErrorCallback process_error_callback_; + + LaunchType launch_type_ = LaunchType::CHILD; + + DISALLOW_COPY_AND_ASSIGN(MojoTestBase); +}; + +// Launches a new child process running the test client |client_name| connected +// to a new message pipe bound to |pipe_name|. |pipe_name| is automatically +// closed on test teardown. +#define RUN_CHILD_ON_PIPE(client_name, pipe_name) \ + StartClientWithHandler( \ + #client_name, \ + [&](MojoHandle pipe_name, int *expected_exit_code) { { + +// Waits for the client to terminate and expects a return code of zero. +#define END_CHILD() \ + } \ + *expected_exit_code = 0; \ + }); + +// Wait for the client to terminate with a specific return code. +#define END_CHILD_AND_EXPECT_EXIT_CODE(code) \ + } \ + *expected_exit_code = code; \ + }); + +// Use this to declare the child process's "main()" function for tests using +// MojoTestBase and MultiprocessTestHelper. It returns an |int|, which will +// will be the process's exit code (but see the comment about +// WaitForChildShutdown()). +// +// The function is defined as a subclass of |test_base| to facilitate shared +// code between test clients and to allow clients to spawn children themselves. +// +// |pipe_name| will be bound to the MojoHandle of a message pipe connected +// to the parent process (see RUN_CHILD_ON_PIPE above.) This pipe handle is +// automatically closed on test client teardown. +#if !defined(OS_IOS) +#define DEFINE_TEST_CLIENT_WITH_PIPE(client_name, test_base, pipe_name) \ + class client_name##_MainFixture : public test_base { \ + void TestBody() override {} \ + public: \ + int Main(MojoHandle); \ + }; \ + MULTIPROCESS_TEST_MAIN_WITH_SETUP( \ + client_name##TestChildMain, \ + ::mojo::edk::test::MultiprocessTestHelper::ChildSetup) { \ + client_name##_MainFixture test; \ + return ::mojo::edk::test::MultiprocessTestHelper::RunClientMain( \ + base::Bind(&client_name##_MainFixture::Main, \ + base::Unretained(&test))); \ + } \ + int client_name##_MainFixture::Main(MojoHandle pipe_name) + +// This is a version of DEFINE_TEST_CLIENT_WITH_PIPE which can be used with +// gtest ASSERT/EXPECT macros. +#define DEFINE_TEST_CLIENT_TEST_WITH_PIPE(client_name, test_base, pipe_name) \ + class client_name##_MainFixture : public test_base { \ + void TestBody() override {} \ + public: \ + void Main(MojoHandle); \ + }; \ + MULTIPROCESS_TEST_MAIN_WITH_SETUP( \ + client_name##TestChildMain, \ + ::mojo::edk::test::MultiprocessTestHelper::ChildSetup) { \ + client_name##_MainFixture test; \ + return ::mojo::edk::test::MultiprocessTestHelper::RunClientTestMain( \ + base::Bind(&client_name##_MainFixture::Main, \ + base::Unretained(&test))); \ + } \ + void client_name##_MainFixture::Main(MojoHandle pipe_name) +#else // !defined(OS_IOS) +#define DEFINE_TEST_CLIENT_WITH_PIPE(client_name, test_base, pipe_name) +#define DEFINE_TEST_CLIENT_TEST_WITH_PIPE(client_name, test_base, pipe_name) +#endif // !defined(OS_IOS) + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_TEST_MOJO_TEST_BASE_H_ diff --git a/mojo/edk/test/multiprocess_test_helper.cc b/mojo/edk/test/multiprocess_test_helper.cc new file mode 100644 index 0000000000000000000000000000000000000000..cf377827f577edc08f2c210a1e859bc27f610a9e --- /dev/null +++ b/mojo/edk/test/multiprocess_test_helper.cc @@ -0,0 +1,263 @@ +// Copyright 2013 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. + +#include "mojo/edk/test/multiprocess_test_helper.h" + +#include +#include +#include + +#include "base/base_paths.h" +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/path_service.h" +#include "base/process/kill.h" +#include "base/process/process_handle.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/named_platform_handle_utils.h" +#include "mojo/edk/embedder/pending_process_connection.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#elif defined(OS_MACOSX) && !defined(OS_IOS) +#include "base/mac/mach_port_broker.h" +#endif + +namespace mojo { +namespace edk { +namespace test { + +namespace { + +const char kMojoPrimordialPipeToken[] = "mojo-primordial-pipe-token"; +const char kMojoNamedPipeName[] = "mojo-named-pipe-name"; + +template +int RunClientFunction(Func handler, bool pass_pipe_ownership_to_main) { + CHECK(MultiprocessTestHelper::primordial_pipe.is_valid()); + ScopedMessagePipeHandle pipe = + std::move(MultiprocessTestHelper::primordial_pipe); + MessagePipeHandle pipe_handle = + pass_pipe_ownership_to_main ? pipe.release() : pipe.get(); + return handler(pipe_handle.value()); +} + +} // namespace + +MultiprocessTestHelper::MultiprocessTestHelper() {} + +MultiprocessTestHelper::~MultiprocessTestHelper() { + CHECK(!test_child_.process.IsValid()); +} + +ScopedMessagePipeHandle MultiprocessTestHelper::StartChild( + const std::string& test_child_name, + LaunchType launch_type) { + return StartChildWithExtraSwitch(test_child_name, std::string(), + std::string(), launch_type); +} + +ScopedMessagePipeHandle MultiprocessTestHelper::StartChildWithExtraSwitch( + const std::string& test_child_name, + const std::string& switch_string, + const std::string& switch_value, + LaunchType launch_type) { + CHECK(!test_child_name.empty()); + CHECK(!test_child_.process.IsValid()); + + std::string test_child_main = test_child_name + "TestChildMain"; + + // Manually construct the new child's commandline to avoid copying unwanted + // values. + base::CommandLine command_line( + base::GetMultiProcessTestChildBaseCommandLine().GetProgram()); + + std::set uninherited_args; + uninherited_args.insert("mojo-platform-channel-handle"); + uninherited_args.insert(switches::kTestChildProcess); + + // Copy commandline switches from the parent process, except for the + // multiprocess client name and mojo message pipe handle; this allows test + // clients to spawn other test clients. + for (const auto& entry : + base::CommandLine::ForCurrentProcess()->GetSwitches()) { + if (uninherited_args.find(entry.first) == uninherited_args.end()) + command_line.AppendSwitchNative(entry.first, entry.second); + } + + PlatformChannelPair channel; + NamedPlatformHandle named_pipe; + HandlePassingInformation handle_passing_info; + if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) { + channel.PrepareToPassClientHandleToChildProcess(&command_line, + &handle_passing_info); + } else if (launch_type == LaunchType::NAMED_CHILD || + launch_type == LaunchType::NAMED_PEER) { +#if defined(OS_POSIX) + base::FilePath temp_dir; + CHECK(base::PathService::Get(base::DIR_TEMP, &temp_dir)); + named_pipe = NamedPlatformHandle( + temp_dir.AppendASCII(GenerateRandomToken()).value()); +#else + named_pipe = NamedPlatformHandle(GenerateRandomToken()); +#endif + command_line.AppendSwitchNative(kMojoNamedPipeName, named_pipe.name); + } + + if (!switch_string.empty()) { + CHECK(!command_line.HasSwitch(switch_string)); + if (!switch_value.empty()) + command_line.AppendSwitchASCII(switch_string, switch_value); + else + command_line.AppendSwitch(switch_string); + } + + base::LaunchOptions options; +#if defined(OS_POSIX) + options.fds_to_remap = &handle_passing_info; +#elif defined(OS_WIN) + options.start_hidden = true; + if (base::win::GetVersion() >= base::win::VERSION_VISTA) + options.handles_to_inherit = &handle_passing_info; + else + options.inherit_handles = true; +#else +#error "Not supported yet." +#endif + + // NOTE: In the case of named pipes, it's important that the server handle be + // created before the child process is launched; otherwise the server binding + // the pipe path can race with child's connection to the pipe. + ScopedPlatformHandle server_handle; + if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) { + server_handle = channel.PassServerHandle(); + } else if (launch_type == LaunchType::NAMED_CHILD || + launch_type == LaunchType::NAMED_PEER) { + server_handle = CreateServerHandle(named_pipe); + } + + PendingProcessConnection process; + ScopedMessagePipeHandle pipe; + if (launch_type == LaunchType::CHILD || + launch_type == LaunchType::NAMED_CHILD) { + std::string pipe_token; + pipe = process.CreateMessagePipe(&pipe_token); + command_line.AppendSwitchASCII(kMojoPrimordialPipeToken, pipe_token); + } else if (launch_type == LaunchType::PEER || + launch_type == LaunchType::NAMED_PEER) { + peer_token_ = mojo::edk::GenerateRandomToken(); + pipe = ConnectToPeerProcess(std::move(server_handle), peer_token_); + } + + test_child_ = + base::SpawnMultiProcessTestChild(test_child_main, command_line, options); + if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) + channel.ChildProcessLaunched(); + + if (launch_type == LaunchType::CHILD || + launch_type == LaunchType::NAMED_CHILD) { + DCHECK(server_handle.is_valid()); + process.Connect(test_child_.process.Handle(), + ConnectionParams(std::move(server_handle)), + process_error_callback_); + } + + CHECK(test_child_.process.IsValid()); + return pipe; +} + +int MultiprocessTestHelper::WaitForChildShutdown() { + CHECK(test_child_.process.IsValid()); + + int rv = -1; + WaitForMultiprocessTestChildExit(test_child_.process, + TestTimeouts::action_timeout(), &rv); + test_child_.process.Close(); + return rv; +} + +void MultiprocessTestHelper::ClosePeerConnection() { + DCHECK(!peer_token_.empty()); + ::mojo::edk::ClosePeerConnection(peer_token_); + peer_token_.clear(); +} + +bool MultiprocessTestHelper::WaitForChildTestShutdown() { + return WaitForChildShutdown() == 0; +} + +// static +void MultiprocessTestHelper::ChildSetup() { + CHECK(base::CommandLine::InitializedForCurrentProcess()); + + std::string primordial_pipe_token = + base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + kMojoPrimordialPipeToken); + NamedPlatformHandle named_pipe( + base::CommandLine::ForCurrentProcess()->GetSwitchValueNative( + kMojoNamedPipeName)); + if (!primordial_pipe_token.empty()) { + primordial_pipe = CreateChildMessagePipe(primordial_pipe_token); +#if defined(OS_MACOSX) && !defined(OS_IOS) + CHECK(base::MachPortBroker::ChildSendTaskPortToParent("mojo_test")); +#endif + if (named_pipe.is_valid()) { + SetParentPipeHandle(CreateClientHandle(named_pipe)); + } else { + SetParentPipeHandle( + PlatformChannelPair::PassClientHandleFromParentProcess( + *base::CommandLine::ForCurrentProcess())); + } + } else { + if (named_pipe.is_valid()) { + primordial_pipe = ConnectToPeerProcess(CreateClientHandle(named_pipe)); + } else { + primordial_pipe = ConnectToPeerProcess( + PlatformChannelPair::PassClientHandleFromParentProcess( + *base::CommandLine::ForCurrentProcess())); + } + } +} + +// static +int MultiprocessTestHelper::RunClientMain( + const base::Callback& main, + bool pass_pipe_ownership_to_main) { + return RunClientFunction( + [main](MojoHandle handle) { return main.Run(handle); }, + pass_pipe_ownership_to_main); +} + +// static +int MultiprocessTestHelper::RunClientTestMain( + const base::Callback& main) { + return RunClientFunction( + [main](MojoHandle handle) { + main.Run(handle); + return (::testing::Test::HasFatalFailure() || + ::testing::Test::HasNonfatalFailure()) + ? 1 + : 0; + }, + true /* close_pipe_on_exit */); +} + +// static +mojo::ScopedMessagePipeHandle MultiprocessTestHelper::primordial_pipe; + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/multiprocess_test_helper.h b/mojo/edk/test/multiprocess_test_helper.h new file mode 100644 index 0000000000000000000000000000000000000000..dc1c9bcc18b86a2f4c12048fd32c29553dcfbc34 --- /dev/null +++ b/mojo/edk/test/multiprocess_test_helper.h @@ -0,0 +1,110 @@ +// Copyright 2013 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 MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_ +#define MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_ + +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/process/process.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "testing/multiprocess_func_list.h" + +namespace mojo { + +namespace edk { + +namespace test { + +class MultiprocessTestHelper { + public: + using HandlerCallback = base::Callback; + + enum class LaunchType { + // Launch the child process as a child in the mojo system. + CHILD, + + // Launch the child process as an unrelated peer process in the mojo system. + PEER, + + // Launch the child process as a child in the mojo system, using a named + // pipe. + NAMED_CHILD, + + // Launch the child process as an unrelated peer process in the mojo + // system, using a named pipe. + NAMED_PEER, + }; + + MultiprocessTestHelper(); + ~MultiprocessTestHelper(); + + // Start a child process and run the "main" function "named" |test_child_name| + // declared using |MOJO_MULTIPROCESS_TEST_CHILD_MAIN()| or + // |MOJO_MULTIPROCESS_TEST_CHILD_TEST()| (below). + ScopedMessagePipeHandle StartChild( + const std::string& test_child_name, + LaunchType launch_type = LaunchType::CHILD); + + // Like |StartChild()|, but appends an extra switch (with ASCII value) to the + // command line. (The switch must not already be present in the default + // command line.) + ScopedMessagePipeHandle StartChildWithExtraSwitch( + const std::string& test_child_name, + const std::string& switch_string, + const std::string& switch_value, + LaunchType launch_type); + + void set_process_error_callback(const ProcessErrorCallback& callback) { + process_error_callback_ = callback; + } + + void ClosePeerConnection(); + + // Wait for the child process to terminate. + // Returns the exit code of the child process. Note that, though it's declared + // to be an |int|, the exit code is subject to mangling by the OS. E.g., we + // usually return -1 on error in the child (e.g., if |test_child_name| was not + // found), but this is mangled to 255 on Linux. You should only rely on codes + // 0-127 being preserved, and -1 being outside the range 0-127. + int WaitForChildShutdown(); + + // Like |WaitForChildShutdown()|, but returns true on success (exit code of 0) + // and false otherwise. You probably want to do something like + // |EXPECT_TRUE(WaitForChildTestShutdown());|. + bool WaitForChildTestShutdown(); + + const base::Process& test_child() const { return test_child_.process; } + + // Used by macros in mojo/edk/test/mojo_test_base.h to support multiprocess + // test client initialization. + static void ChildSetup(); + static int RunClientMain(const base::Callback& main, + bool pass_pipe_ownership_to_main = false); + static int RunClientTestMain(const base::Callback& main); + + // For use (and only valid) in the child process: + static mojo::ScopedMessagePipeHandle primordial_pipe; + + private: + // Valid after |StartChild()| and before |WaitForChildShutdown()|. + base::SpawnChildResult test_child_; + + ProcessErrorCallback process_error_callback_; + + std::string peer_token_; + + DISALLOW_COPY_AND_ASSIGN(MultiprocessTestHelper); +}; + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_ diff --git a/mojo/edk/test/multiprocess_test_helper_unittest.cc b/mojo/edk/test/multiprocess_test_helper_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..f7e9e832f401a6e9da0ad06350fef841d8760684 --- /dev/null +++ b/mojo/edk/test/multiprocess_test_helper_unittest.cc @@ -0,0 +1,165 @@ +// Copyright 2013 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. + +#include "mojo/edk/test/multiprocess_test_helper.h" + +#include + +#include + +#include "base/logging.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_POSIX) +#include +#endif + +namespace mojo { +namespace edk { +namespace test { +namespace { + +bool IsNonBlocking(const PlatformHandle& handle) { +#if defined(OS_WIN) + // Haven't figured out a way to query whether a HANDLE was created with + // FILE_FLAG_OVERLAPPED. + return true; +#else + return fcntl(handle.handle, F_GETFL) & O_NONBLOCK; +#endif +} + +bool WriteByte(const PlatformHandle& handle, char c) { + size_t bytes_written = 0; + BlockingWrite(handle, &c, 1, &bytes_written); + return bytes_written == 1; +} + +bool ReadByte(const PlatformHandle& handle, char* c) { + size_t bytes_read = 0; + BlockingRead(handle, c, 1, &bytes_read); + return bytes_read == 1; +} + +using MultiprocessTestHelperTest = testing::Test; + +TEST_F(MultiprocessTestHelperTest, RunChild) { + MultiprocessTestHelper helper; + EXPECT_TRUE(helper.server_platform_handle.is_valid()); + + helper.StartChild("RunChild"); + EXPECT_EQ(123, helper.WaitForChildShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_MAIN(RunChild) { + CHECK(MultiprocessTestHelper::client_platform_handle.is_valid()); + return 123; +} + +TEST_F(MultiprocessTestHelperTest, TestChildMainNotFound) { + MultiprocessTestHelper helper; + helper.StartChild("NoSuchTestChildMain"); + int result = helper.WaitForChildShutdown(); + EXPECT_FALSE(result >= 0 && result <= 127); +} + +TEST_F(MultiprocessTestHelperTest, PassedChannel) { + MultiprocessTestHelper helper; + EXPECT_TRUE(helper.server_platform_handle.is_valid()); + helper.StartChild("PassedChannel"); + + // Take ownership of the handle. + ScopedPlatformHandle handle = std::move(helper.server_platform_handle); + + // The handle should be non-blocking. + EXPECT_TRUE(IsNonBlocking(handle.get())); + + // Write a byte. + const char c = 'X'; + EXPECT_TRUE(WriteByte(handle.get(), c)); + + // It'll echo it back to us, incremented. + char d = 0; + EXPECT_TRUE(ReadByte(handle.get(), &d)); + EXPECT_EQ(c + 1, d); + + // And return it, incremented again. + EXPECT_EQ(c + 2, helper.WaitForChildShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_MAIN(PassedChannel) { + CHECK(MultiprocessTestHelper::client_platform_handle.is_valid()); + + // Take ownership of the handle. + ScopedPlatformHandle handle = + std::move(MultiprocessTestHelper::client_platform_handle); + + // The handle should be non-blocking. + EXPECT_TRUE(IsNonBlocking(handle.get())); + + // Read a byte. + char c = 0; + EXPECT_TRUE(ReadByte(handle.get(), &c)); + + // Write it back, incremented. + c++; + EXPECT_TRUE(WriteByte(handle.get(), c)); + + // And return it, incremented again. + c++; + return static_cast(c); +} + +TEST_F(MultiprocessTestHelperTest, ChildTestPasses) { + MultiprocessTestHelper helper; + EXPECT_TRUE(helper.server_platform_handle.is_valid()); + helper.StartChild("ChildTestPasses"); + EXPECT_TRUE(helper.WaitForChildTestShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestPasses) { + ASSERT_TRUE(MultiprocessTestHelper::client_platform_handle.is_valid()); + EXPECT_TRUE( + IsNonBlocking(MultiprocessTestHelper::client_platform_handle.get())); +} + +TEST_F(MultiprocessTestHelperTest, ChildTestFailsAssert) { + MultiprocessTestHelper helper; + EXPECT_TRUE(helper.server_platform_handle.is_valid()); + helper.StartChild("ChildTestFailsAssert"); + EXPECT_FALSE(helper.WaitForChildTestShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestFailsAssert) { + ASSERT_FALSE(MultiprocessTestHelper::client_platform_handle.is_valid()) + << "DISREGARD: Expected failure in child process"; + ASSERT_FALSE( + IsNonBlocking(MultiprocessTestHelper::client_platform_handle.get())) + << "Not reached"; + CHECK(false) << "Not reached"; +} + +TEST_F(MultiprocessTestHelperTest, ChildTestFailsExpect) { + MultiprocessTestHelper helper; + EXPECT_TRUE(helper.server_platform_handle.is_valid()); + helper.StartChild("ChildTestFailsExpect"); + EXPECT_FALSE(helper.WaitForChildTestShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestFailsExpect) { + EXPECT_FALSE(MultiprocessTestHelper::client_platform_handle.is_valid()) + << "DISREGARD: Expected failure #1 in child process"; + EXPECT_FALSE( + IsNonBlocking(MultiprocessTestHelper::client_platform_handle.get())) + << "DISREGARD: Expected failure #2 in child process"; +} + +} // namespace +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/run_all_perftests.cc b/mojo/edk/test/run_all_perftests.cc new file mode 100644 index 0000000000000000000000000000000000000000..3ce3b470995984f606e693a280bd623be68251cf --- /dev/null +++ b/mojo/edk/test/run_all_perftests.cc @@ -0,0 +1,26 @@ +// Copyright 2013 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. + +#include "base/command_line.h" +#include "base/test/multiprocess_test.h" +#include "base/test/perf_test_suite.h" +#include "base/test/test_io_thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" +#include "mojo/edk/test/multiprocess_test_helper.h" +#include "mojo/edk/test/test_support_impl.h" +#include "mojo/public/tests/test_support_private.h" + +int main(int argc, char** argv) { + base::PerfTestSuite test(argc, argv); + + mojo::edk::Init(); + base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart); + mojo::edk::ScopedIPCSupport ipc_support( + test_io_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + mojo::test::TestSupport::Init(new mojo::edk::test::TestSupportImpl()); + + return test.Run(); +} diff --git a/mojo/edk/test/run_all_unittests.cc b/mojo/edk/test/run_all_unittests.cc new file mode 100644 index 0000000000000000000000000000000000000000..a057825d8f09076a1c07aee82244d3f583a999d0 --- /dev/null +++ b/mojo/edk/test/run_all_unittests.cc @@ -0,0 +1,49 @@ +// Copyright 2013 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. + +#include + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_io_thread.h" +#include "base/test/test_suite.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" +#include "mojo/edk/test/multiprocess_test_helper.h" +#include "mojo/edk/test/test_support_impl.h" +#include "mojo/public/tests/test_support_private.h" +#include "testing/gtest/include/gtest/gtest.h" + +int main(int argc, char** argv) { +#if !defined(OS_ANDROID) + // Silence death test thread warnings on Linux. We can afford to run our death + // tests a little more slowly (< 10 ms per death test on a Z620). + // On android, we need to run in the default mode, as the threadsafe mode + // relies on execve which is not available. + testing::GTEST_FLAG(death_test_style) = "threadsafe"; +#endif +#if defined(OS_ANDROID) + // On android, the test framework has a signal handler that will print a + // [ CRASH ] line when the application crashes. This breaks death test has the + // test runner will consider the death of the child process a test failure. + // Removing the signal handler solves this issue. + signal(SIGABRT, SIG_DFL); +#endif + + base::TestSuite test_suite(argc, argv); + + mojo::edk::Init(); + + mojo::test::TestSupport::Init(new mojo::edk::test::TestSupportImpl()); + base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart); + + mojo::edk::ScopedIPCSupport ipc_support( + test_io_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + return base::LaunchUnitTests( + argc, argv, + base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/mojo/edk/test/test_support_impl.cc b/mojo/edk/test/test_support_impl.cc new file mode 100644 index 0000000000000000000000000000000000000000..25684e4805ae5ab0a01679ff3b95ffdd51a391fe --- /dev/null +++ b/mojo/edk/test/test_support_impl.cc @@ -0,0 +1,84 @@ +// Copyright 2014 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. + +#include "mojo/edk/test/test_support_impl.h" + +#include +#include +#include + +#include + +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/test/perf_log.h" + +namespace mojo { +namespace edk { +namespace test { +namespace { + +base::FilePath ResolveSourceRootRelativePath(const char* relative_path) { + base::FilePath path; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &path)) + return base::FilePath(); + + for (const base::StringPiece& component : base::SplitStringPiece( + relative_path, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { + if (!component.empty()) + path = path.AppendASCII(component); + } + + return path; +} + +} // namespace + +TestSupportImpl::TestSupportImpl() { +} + +TestSupportImpl::~TestSupportImpl() { +} + +void TestSupportImpl::LogPerfResult(const char* test_name, + const char* sub_test_name, + double value, + const char* units) { + DCHECK(test_name); + if (sub_test_name) { + std::string name = base::StringPrintf("%s/%s", test_name, sub_test_name); + base::LogPerfResult(name.c_str(), value, units); + } else { + base::LogPerfResult(test_name, value, units); + } +} + +FILE* TestSupportImpl::OpenSourceRootRelativeFile(const char* relative_path) { + return base::OpenFile(ResolveSourceRootRelativePath(relative_path), "rb"); +} + +char** TestSupportImpl::EnumerateSourceRootRelativeDirectory( + const char* relative_path) { + std::vector names; + base::FileEnumerator e(ResolveSourceRootRelativePath(relative_path), false, + base::FileEnumerator::FILES); + for (base::FilePath name = e.Next(); !name.empty(); name = e.Next()) + names.push_back(name.BaseName().AsUTF8Unsafe()); + + // |names.size() + 1| for null terminator. + char** rv = static_cast(calloc(names.size() + 1, sizeof(char*))); + for (size_t i = 0; i < names.size(); ++i) + rv[i] = base::strdup(names[i].c_str()); + return rv; +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/test_support_impl.h b/mojo/edk/test/test_support_impl.h new file mode 100644 index 0000000000000000000000000000000000000000..ebb5ce658a82e6f8b0f6f85cc882a387557938e8 --- /dev/null +++ b/mojo/edk/test/test_support_impl.h @@ -0,0 +1,38 @@ +// Copyright 2014 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 MOJO_EDK_TEST_TEST_SUPPORT_IMPL_H_ +#define MOJO_EDK_TEST_TEST_SUPPORT_IMPL_H_ + +#include + +#include "base/macros.h" +#include "mojo/public/tests/test_support_private.h" + +namespace mojo { +namespace edk { +namespace test { + +class TestSupportImpl : public mojo::test::TestSupport { + public: + TestSupportImpl(); + ~TestSupportImpl() override; + + void LogPerfResult(const char* test_name, + const char* sub_test_name, + double value, + const char* units) override; + FILE* OpenSourceRootRelativeFile(const char* relative_path) override; + char** EnumerateSourceRootRelativeDirectory( + const char* relative_path) override; + + private: + DISALLOW_COPY_AND_ASSIGN(TestSupportImpl); +}; + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_TEST_TEST_SUPPORT_IMPL_H_ diff --git a/mojo/edk/test/test_utils.h b/mojo/edk/test/test_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..939171b26f88e7f5f088db76fbad2795f94d6d16 --- /dev/null +++ b/mojo/edk/test/test_utils.h @@ -0,0 +1,55 @@ +// Copyright 2014 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 MOJO_EDK_TEST_TEST_UTILS_H_ +#define MOJO_EDK_TEST_TEST_UTILS_H_ + +#include +#include + +#include + +#include "base/files/scoped_file.h" +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" + +namespace mojo { +namespace edk { +namespace test { + +// On success, |bytes_written| is updated to the number of bytes written; +// otherwise it is untouched. +bool BlockingWrite(const PlatformHandle& handle, + const void* buffer, + size_t bytes_to_write, + size_t* bytes_written); + +// On success, |bytes_read| is updated to the number of bytes read; otherwise it +// is untouched. +bool BlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read); + +// If the read is done successfully or would block, the function returns true +// and updates |bytes_read| to the number of bytes read (0 if the read would +// block); otherwise it returns false and leaves |bytes_read| untouched. +// |handle| must already be in non-blocking mode. +bool NonBlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read); + +// Gets a (scoped) |PlatformHandle| from the given (scoped) |FILE|. +ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp); + +// Gets a (scoped) |FILE| from a (scoped) |PlatformHandle|. +base::ScopedFILE FILEFromPlatformHandle(ScopedPlatformHandle h, + const char* mode); + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_TEST_TEST_UTILS_H_ diff --git a/mojo/edk/test/test_utils_posix.cc b/mojo/edk/test/test_utils_posix.cc new file mode 100644 index 0000000000000000000000000000000000000000..60d5db59db0063057c155c9da5e0de46ac0fc4f8 --- /dev/null +++ b/mojo/edk/test/test_utils_posix.cc @@ -0,0 +1,94 @@ +// Copyright 2014 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. + +#include "mojo/edk/test/test_utils.h" + +#include +#include +#include + +#include "base/posix/eintr_wrapper.h" + +namespace mojo { +namespace edk { +namespace test { + +bool BlockingWrite(const PlatformHandle& handle, + const void* buffer, + size_t bytes_to_write, + size_t* bytes_written) { + int original_flags = fcntl(handle.handle, F_GETFL); + if (original_flags == -1 || + fcntl(handle.handle, F_SETFL, original_flags & (~O_NONBLOCK)) != 0) { + return false; + } + + ssize_t result = HANDLE_EINTR(write(handle.handle, buffer, bytes_to_write)); + + fcntl(handle.handle, F_SETFL, original_flags); + + if (result < 0) + return false; + + *bytes_written = result; + return true; +} + +bool BlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read) { + int original_flags = fcntl(handle.handle, F_GETFL); + if (original_flags == -1 || + fcntl(handle.handle, F_SETFL, original_flags & (~O_NONBLOCK)) != 0) { + return false; + } + + ssize_t result = HANDLE_EINTR(read(handle.handle, buffer, buffer_size)); + + fcntl(handle.handle, F_SETFL, original_flags); + + if (result < 0) + return false; + + *bytes_read = result; + return true; +} + +bool NonBlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read) { + ssize_t result = HANDLE_EINTR(read(handle.handle, buffer, buffer_size)); + + if (result < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) + return false; + + *bytes_read = 0; + } else { + *bytes_read = result; + } + + return true; +} + +ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp) { + CHECK(fp); + int rv = dup(fileno(fp.get())); + PCHECK(rv != -1) << "dup"; + return ScopedPlatformHandle(PlatformHandle(rv)); +} + +base::ScopedFILE FILEFromPlatformHandle(ScopedPlatformHandle h, + const char* mode) { + CHECK(h.is_valid()); + base::ScopedFILE rv(fdopen(h.release().handle, mode)); + PCHECK(rv) << "fdopen"; + return rv; +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/test_utils_win.cc b/mojo/edk/test/test_utils_win.cc new file mode 100644 index 0000000000000000000000000000000000000000..17bf5bbfa3ea4a9e1e31bb3cbdaccc9a06ab8a3d --- /dev/null +++ b/mojo/edk/test/test_utils_win.cc @@ -0,0 +1,115 @@ +// Copyright 2014 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. + +#include "mojo/edk/test/test_utils.h" + +#include +#include +#include +#include +#include + +namespace mojo { +namespace edk { +namespace test { + +bool BlockingWrite(const PlatformHandle& handle, + const void* buffer, + size_t bytes_to_write, + size_t* bytes_written) { + OVERLAPPED overlapped = {0}; + DWORD bytes_written_dword = 0; + + if (!WriteFile(handle.handle, buffer, static_cast(bytes_to_write), + &bytes_written_dword, &overlapped)) { + if (GetLastError() != ERROR_IO_PENDING || + !GetOverlappedResult(handle.handle, &overlapped, &bytes_written_dword, + TRUE)) { + return false; + } + } + + *bytes_written = bytes_written_dword; + return true; +} + +bool BlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read) { + OVERLAPPED overlapped = {0}; + DWORD bytes_read_dword = 0; + + if (!ReadFile(handle.handle, buffer, static_cast(buffer_size), + &bytes_read_dword, &overlapped)) { + if (GetLastError() != ERROR_IO_PENDING || + !GetOverlappedResult(handle.handle, &overlapped, &bytes_read_dword, + TRUE)) { + return false; + } + } + + *bytes_read = bytes_read_dword; + return true; +} + +bool NonBlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read) { + OVERLAPPED overlapped = {0}; + DWORD bytes_read_dword = 0; + + if (!ReadFile(handle.handle, buffer, static_cast(buffer_size), + &bytes_read_dword, &overlapped)) { + if (GetLastError() != ERROR_IO_PENDING) + return false; + + CancelIo(handle.handle); + + if (!GetOverlappedResult(handle.handle, &overlapped, &bytes_read_dword, + TRUE)) { + *bytes_read = 0; + return true; + } + } + + *bytes_read = bytes_read_dword; + return true; +} + +ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp) { + CHECK(fp); + + HANDLE rv = INVALID_HANDLE_VALUE; + PCHECK(DuplicateHandle( + GetCurrentProcess(), + reinterpret_cast(_get_osfhandle(_fileno(fp.get()))), + GetCurrentProcess(), &rv, 0, TRUE, DUPLICATE_SAME_ACCESS)) + << "DuplicateHandle"; + return ScopedPlatformHandle(PlatformHandle(rv)); +} + +base::ScopedFILE FILEFromPlatformHandle(ScopedPlatformHandle h, + const char* mode) { + CHECK(h.is_valid()); + // Microsoft's documentation for |_open_osfhandle()| only discusses these + // flags (and |_O_WTEXT|). Hmmm. + int flags = 0; + if (strchr(mode, 'a')) + flags |= _O_APPEND; + if (strchr(mode, 'r')) + flags |= _O_RDONLY; + if (strchr(mode, 't')) + flags |= _O_TEXT; + base::ScopedFILE rv(_fdopen( + _open_osfhandle(reinterpret_cast(h.release().handle), flags), + mode)); + PCHECK(rv) << "_fdopen"; + return rv; +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/public/BUILD.gn b/mojo/public/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..3baf6670645c26be4e081687c80feada71ce120e --- /dev/null +++ b/mojo/public/BUILD.gn @@ -0,0 +1,28 @@ +# Copyright 2014 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. + +group("public") { + # Meta-target, don't link into production code. + testonly = true + deps = [ + ":sdk", + "cpp/bindings", + "interfaces/bindings/tests:test_interfaces", + ] + + if (is_android) { + deps += [ + "java:bindings_java", + "java:system_java", + ] + } +} + +group("sdk") { + deps = [ + "c/system", + "cpp/bindings", + "js", + ] +} diff --git a/mojo/public/DEPS b/mojo/public/DEPS new file mode 100644 index 0000000000000000000000000000000000000000..2e499957417d91ad692afcff68dc518bde5408b6 --- /dev/null +++ b/mojo/public/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + # This code is checked into the chromium repo so it's fine to depend on this. + "+base", + "+build", + "+testing", + + "+ipc/ipc_param_traits.h", + + # internal includes. + "+mojo", +] diff --git a/mojo/public/LICENSE b/mojo/public/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..972bb2edb099e58b6a1939fa57f8e74391159c0c --- /dev/null +++ b/mojo/public/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/mojo/public/c/system/BUILD.gn b/mojo/public/c/system/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..08185c75141fd13ef018343ba7d4028c3d4c747c --- /dev/null +++ b/mojo/public/c/system/BUILD.gn @@ -0,0 +1,37 @@ +# Copyright 2014 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. + +component("system") { + output_name = "mojo_public_system" + + sources = [ + "buffer.h", + "core.h", + "data_pipe.h", + "functions.h", + "macros.h", + "message_pipe.h", + "platform_handle.h", + "system_export.h", + "thunks.cc", + "thunks.h", + "types.h", + "watcher.h", + ] + + defines = [ "MOJO_SYSTEM_IMPLEMENTATION" ] +} + +# This should ONLY be depended upon directly by shared_library targets which +# need to export the MojoSetSystemThunks symbol, like targets generated by the +# mojo_native_application template in //services/service_manager/public/cpp/service.gni. +source_set("set_thunks_for_app") { + sources = [ + "set_thunks_for_app.cc", + ] + + public_deps = [ + ":system", + ] +} diff --git a/mojo/public/c/system/README.md b/mojo/public/c/system/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2abe80ffc730aa3dad08d87f2bd98d05b77740d4 --- /dev/null +++ b/mojo/public/c/system/README.md @@ -0,0 +1,869 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C System API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview +The Mojo C System API is a lightweight API (with an eventually-stable ABI) upon +which all higher layers of the Mojo system are built. + +This API exposes the fundamental capabilities to: create, read from, and write +to **message pipes**; create, read from, and write to **data pipes**; create +**shared buffers** and generate sharable handles to them; wrap platform-specific +handle objects (such as **file descriptors**, **Windows handles**, and +**Mach ports**) for seamless transit over message pipes; and efficiently watch +handles for various types of state transitions. + +This document provides a brief guide to API usage with example code snippets. +For a detailed API references please consult the headers in +[//mojo/public/c/system](https://cs.chromium.org/chromium/src/mojo/public/c/system/). + +### A Note About Multithreading + +The Mojo C System API is entirely thread-agnostic. This means that all functions +may be called from any thread in a process, and there are no restrictions on how +many threads can use the same object at the same time. + +Of course this does not mean you can completely ignore potential concurrency +issues -- such as a handle being closed on one thread while another thread is +trying to perform an operation on the same handle -- but there is nothing +fundamentally incorrect about using any given API or handle from multiple +threads. + +### A Note About Synchronization + +Every Mojo API call is non-blocking and synchronously yields some kind of status +result code, but the call's side effects -- such as affecting the state of +one or more handles in the system -- may or may not occur asynchronously. + +Mojo objects can be observed for interesting state changes in a way that is +thread-agnostic and in some ways similar to POSIX signal handlers: *i.e.* +user-provided notification handlers may be invoked at any time on arbitrary +threads in the process. It is entirely up to the API user to take appropriate +measures to synchronize operations against other application state. + +The higher level [system](/mojo#High-Level-System-APIs) and +[bindings](/mojo#High-Level-Bindings-APIs) APIs provide helpers to simplify Mojo +usage in this regard, at the expense of some flexibility. + +## Result Codes + +Most API functions return a value of type `MojoResult`. This is an integral +result code used to convey some meaningful level of detail about the result of a +requested operation. + +See [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h) +for different possible values. See documentation for individual API calls for +more specific contextual meaning of various result codes. + +## Handles + +Every Mojo IPC primitive is identified by a generic, opaque integer handle of +type `MojoHandle`. Handles can be acquired by creating new objects using various +API calls, or by reading messages which contain attached handles. + +A `MojoHandle` can represent a message pipe endpoint, a data pipe consumer, +a data pipe producer, a shared buffer reference, a wrapped native platform +handle such as a POSIX file descriptor or a Windows system handle, or a watcher +object (see [Signals & Watchers](#Signals-Watchers) below.) + +All types of handles except for watchers (which are an inherently local concept) +can be attached to messages and sent over message pipes. + +Any `MojoHandle` may be closed by calling `MojoClose`: + +``` c +MojoHandle x = DoSomethingToGetAValidHandle(); +MojoResult result = MojoClose(x); +``` + +If the handle passed to `MojoClose` was a valid handle, it will be closed and +`MojoClose` returns `MOJO_RESULT_OK`. Otherwise it returns +`MOJO_RESULT_INVALID_ARGUMENT`. + +Similar to native system handles on various popular platforms, `MojoHandle` +values may be reused over time. Thus it is important to avoid logical errors +which lead to misplaced handle ownership, double-closes, *etc.* + +## Message Pipes + +A message pipe is a bidirectional messaging channel which can carry arbitrary +unstructured binary messages with zero or more `MojoHandle` attachments to be +transferred from one end of a pipe to the other. Message pipes work seamlessly +across process boundaries or within a single process. + +The [Embedder Development Kit (EDK)](/mojo/edk/embedder) provides the means to +bootstrap one or more primordial cross-process message pipes, and it's up to +Mojo embedders to expose this capability in some useful way. Once such a pipe is +established, additional handles -- including other message pipe handles -- may +be sent to a remote process using that pipe (or in turn, over other pipes sent +over that pipe, or pipes sent over *that* pipe, and so on...) + +The public C System API exposes the ability to read and write messages on pipes +and to create new message pipes. + +See [//mojo/public/c/system/message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/message_pipe.h) +for detailed message pipe API documentation. + +### Creating Message Pipes + +`MojoCreateMessagePipe` can be used to create a new message pipe: + +``` c +MojoHandle a, b; +MojoResult result = MojoCreateMessagePipe(NULL, &a, &b); +``` + +After this snippet, `result` should be `MOJO_RESULT_OK` (it's really hard for +this to fail!), and `a` and `b` will contain valid Mojo handles, one for each +end of the new message pipe. + +Any messages written to `a` are eventually readable from `b`, and any messages +written to `b` are eventually readable from `a`. If `a` is closed at any point, +`b` will eventually become aware of this fact; likewise if `b` is closed, `a` +will become aware of that. + +The state of these conditions can be queried and watched asynchronously as +described in the [Signals & Watchers](#Signals-Watchers) section below. + +### Allocating Messages + +In order to avoid redundant internal buffer copies, Mojo would like to allocate +your message storage buffers for you. This is easy: + +``` c +MojoMessageHandle message; +MojoResult result = MojoAllocMessage(6, NULL, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE, + &message); +``` + +Note that we have a special `MojoMessageHandle` type for message objects. + +The code above allocates a buffer for a message payload of 6 bytes with no +handles attached. + +If we change our mind and decide not to send this message, we can delete it: + +``` c +MojoResult result = MojoFreeMessage(message); +``` + +If we instead decide to send our newly allocated message, we first need to fill +in the payload data with something interesting. How about a pleasant greeting: + +``` c +void* buffer = NULL; +MojoResult result = MojoGetMessageBuffer(message, &buffer); +memcpy(buffer, "hello", 6); +``` + +Now we can write the message to a pipe. Note that attempting to write a message +transfers ownership of the message object (and any attached handles) into the +target pipe and there is therefore no need to subsequently call +`MojoFreeMessage` on that message. + +### Writing Messages + +``` c +result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE); +``` + +`MojoWriteMessage` is a *non-blocking* call: it always returns +immediately. If its return code is `MOJO_RESULT_OK` the message will eventually +find its way to the other end of the pipe -- assuming that end isn't closed +first, of course. If the return code is anything else, the message is deleted +and not transferred. + +In this case since we know `b` is still open, we also know the message will +eventually arrive at `b`. `b` can be queried or watched to become aware of when +the message arrives, but we'll ignore that complexity for now. See +[Signals & Watchers](#Signals-Watchers) below for more information. + +*** aside +**NOTE**: Although this is an implementation detail and not strictly guaranteed by the +System API, it is true in the current implementation that the message will +arrive at `b` before the above `MojoWriteMessage` call even returns, because `b` +is in the same process as `a` and has never been transferred over another pipe. +*** + +### Reading Messages + +We can read a new message object from a pipe: + +``` c +MojoMessageHandle message; +uint32_t num_bytes; +MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +and map its buffer to retrieve the contents: + +``` c +void* buffer = NULL; +MojoResult result = MojoGetMessageBuffer(message, &buffer); +printf("Pipe says: %s", (const char*)buffer); +``` + +`result` should be `MOJO_RESULT_OK` and this snippet should write `"hello"` to +`stdout`. + +If we try were to try reading again now that there are no messages on `b`: + +``` c +MojoMessageHandle message; +MojoResult result = MojoReadMessageNew(b, &message, NULL, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +We'll get a `result` of `MOJO_RESULT_SHOULD_WAIT`, indicating that the pipe is +not yet readable. + +### Messages With Handles + +Probably the most useful feature of Mojo IPC is that message pipes can carry +arbitrary Mojo handles, including other message pipes. This is also +straightforward. + +Here's an example which creates two pipes, using the first pipe to transfer +one end of the second pipe. If you have a good imagination you can pretend the +first pipe spans a process boundary, which makes the example more practically +interesting: + +``` c +MojoHandle a, b; +MojoHandle c, d; +MojoMessage message; + +// Allocate a message with an empty payload and handle |c| attached. Note that +// this takes ownership of |c|, effectively invalidating its handle value. +MojoResult result = MojoAllocMessage(0, &c, 1, MOJO_ALLOC_MESSAGE_FLAG_NONE, + message); + +result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE); + +// Some time later... +uint32_t num_bytes; +MojoHandle e; +uint32_t num_handles = 1; +MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, &e, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +At this point the handle in `e` is now referencing the same message pipe +endpoint which was originally referenced by `c`. + +Note that `num_handles` above is initialized to 1 before we pass its address to +`MojoReadMessageNew`. This is to indicate how much `MojoHandle` storage is +available at the output buffer we gave it (`&e` above). + +If we didn't know how many handles to expect in an incoming message -- which is +often the case -- we can use `MojoReadMessageNew` to query for this information +first: + +``` c +MojoMessageHandle message; +uint32_t num_bytes = 0; +uint32_t num_handles = 0; +MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, NULL, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +If in this case there were a received message on `b` with some nonzero number +of handles, `result` would be `MOJO_RESULT_RESOURCE_EXHAUSTED`, and both +`num_bytes` and `num_handles` would be updated to reflect the payload size and +number of attached handles on the next available message. + +It's also worth noting that if there did happen to be a message available with +no payload and no handles (*i.e.* an empty message), this would actually return +`MOJO_RESULT_OK`. + +## Data Pipes + +Data pipes provide an efficient unidirectional channel for moving large amounts +of unframed data between two endpoints. Every data pipe has a fixed +**element size** and **capacity**. Reads and writes must be done in sizes that +are a multiple of the element size, and writes to the pipe can only be queued +up to the pipe's capacity before reads must be done to make more space +available. + +Every data pipe has a single **producer** handle used to write data into the +pipe and a single **consumer** handle used to read data out of the pipe. + +Finally, data pipes support both immediate I/O -- reading into and writing out +from user-supplied buffers -- as well as two-phase I/O, allowing callers to +temporarily lock some portion of the data pipe in order to read or write its +contents directly. + +See [//mojo/public/c/system/data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/data_pipe.h) +for detailed data pipe API documentation. + +### Creating Data Pipes + +Use `MojoCreateDataPipe` to create a new data pipe. The +`MojoCreateDataPipeOptions` structure is used to configure the new pipe, but +this can be omitted to assume the default options of a single-byte element size +and an implementation-defined default capacity (64 kB at the time of this +writing.) + +``` c +MojoHandle producer, consumer; +MojoResult result = MojoCreateDataPipe(NULL, &producer, &consumer); +``` + +### Immediate I/O + +Data can be written into or read out of a data pipe using buffers provided by +the caller. This is generally more convenient than two-phase I/O but is +also less efficient due to extra copying. + +``` c +uint32_t num_bytes = 12; +MojoResult result = MojoWriteData(producer, "datadatadata", &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE); +``` + +The above snippet will attempt to write 12 bytes into the data pipe, which +should succeed and return `MOJO_RESULT_OK`. If the available capacity on the +pipe was less than the amount requested (the input value of `*num_bytes`) this +will copy what it can into the pipe and return the number of bytes written in +`*num_bytes`. If no data could be copied this will instead return +`MOJO_RESULT_SHOULD_WAIT`. + +Reading from the consumer is a similar operation. + +``` c +char buffer[64]; +uint32_t num_bytes = 64; +MojoResult result = MojoReadData(consumer, buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); +``` + +This will attempt to read up to 64 bytes, returning the actual number of bytes +read in `*num_bytes`. + +`MojoReadData` supports a number of interesting flags to change the behavior: +you can peek at the data (copy bytes out without removing them from the pipe), +query the number of bytes available without doing any actual reading of the +contents, or discard data from the pipe without bothering to copy it anywhere. + +This also supports a `MOJO_READ_DATA_FLAG_ALL_OR_NONE` which ensures that the +call succeeds **only** if the exact number of bytes requested could be read. +Otherwise such a request will fail with `MOJO_READ_DATA_OUT_OF_RANGE`. + +### Two-Phase I/O + +Data pipes also support two-phase I/O operations, allowing a caller to +temporarily lock a portion of the data pipe's storage for direct memory access. + +``` c +void* buffer; +uint32_t num_bytes = 1024; +MojoResult result = MojoBeginWriteData(producer, &buffer, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE); +``` + +This requests write access to a region of up to 1024 bytes of the data pipe's +next available capacity. Upon success, `buffer` will point to the writable +storage and `num_bytes` will indicate the size of the buffer there. + +The caller should then write some data into the memory region and release it +ASAP, indicating the number of bytes actually written: + +``` c +memcpy(buffer, "hello", 6); +MojoResult result = MojoEndWriteData(producer, 6); +``` + +Two-phase reads look similar: + +``` c +void* buffer; +uint32_t num_bytes = 1024; +MojoResult result = MojoBeginReadData(consumer, &buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); +// result should be MOJO_RESULT_OK, since there is some data available. + +printf("Pipe says: %s", (const char*)buffer); // Should say "hello". + +result = MojoEndReadData(consumer, 1); // Say we only consumed one byte. + +num_bytes = 1024; +result = MojoBeginReadData(consumer, &buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); +printf("Pipe says: %s", (const char*)buffer); // Should say "ello". +result = MojoEndReadData(consumer, 5); +``` + +## Shared Buffers + +Shared buffers are chunks of memory which can be mapped simultaneously by +multiple processes. Mojo provides a simple API to make these available to +applications. + +See [//mojo/public/c/system/buffer.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/buffer.h) +for detailed shared buffer API documentation. + +### Creating Buffer Handles + +Usage is straightforward. You can create a new buffer: + +``` c +// Allocate a shared buffer of 4 kB. +MojoHandle buffer; +MojoResult result = MojoCreateSharedBuffer(NULL, 4096, &buffer); +``` + +You can also duplicate an existing shared buffer handle: + +``` c +MojoHandle another_name_for_buffer; +MojoResult result = MojoDuplicateBufferHandle(buffer, NULL, + &another_name_for_buffer); +``` + +This is useful if you want to retain a handle to the buffer while also sharing +handles with one or more other clients. The allocated buffer remains valid as +long as at least one shared buffer handle exists to reference it. + +### Mapping Buffers + +You can map (and later unmap) a specified range of the buffer to get direct +memory access to its contents: + +``` c +void* data; +MojoResult result = MojoMapBuffer(buffer, 0, 64, &data, + MOJO_MAP_BUFFER_FLAG_NONE); + +*(int*)data = 42; +result = MojoUnmapBuffer(data); +``` + +A buffer may have any number of active mappings at a time, in any number of +processes. + +### Read-Only Handles + +An option can also be specified on `MojoDuplicateBufferHandle` to ensure +that the newly duplicated handle can only be mapped to read-only memory: + +``` c +MojoHandle read_only_buffer; +MojoDuplicateBufferHandleOptions options; +options.struct_size = sizeof(options); +options.flags = MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY; +MojoResult result = MojoDuplicateBufferHandle(buffer, &options, + &read_only_buffer); + +// Attempt to map and write to the buffer using the read-only handle: +void* data; +result = MojoMapBuffer(read_only_buffer, 0, 64, &data, + MOJO_MAP_BUFFER_FLAG_NONE); +*(int*)data = 42; // CRASH +``` + +*** note +**NOTE:** One important limitation of the current implementation is that +read-only handles can only be produced from a handle that was originally created +by `MojoCreateSharedBuffer` (*i.e.*, you cannot create a read-only duplicate +from a non-read-only duplicate), and the handle cannot have been transferred +over a message pipe first. +*** + +## Native Platform Handles (File Descriptors, Windows Handles, *etc.*) + +Native platform handles to system objects can be wrapped as Mojo handles for +seamless transit over message pipes. Mojo currently supports wrapping POSIX +file descriptors, Windows handles, and Mach ports. + +See [//mojo/public/c/system/platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/platform_handle.h) +for detailed platform handle API documentation. + +### Wrapping Basic Handle Types + +Wrapping a POSIX file descriptor is simple: + +``` c +MojoPlatformHandle platform_handle; +platform_handle.struct_size = sizeof(platform_handle); +platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR; +platform_handle.value = (uint64_t)fd; +MojoHandle handle; +MojoResult result = MojoWrapPlatformHandle(&platform_handle, &handle); +``` + +Note that at this point `handle` effectively owns the file descriptor +and if you were to call `MojoClose(handle)`, the file descriptor would be closed +too; but we're not going to close it here! We're going to pretend we've sent it +over a message pipe, and now we want to unwrap it on the other side: + +``` c +MojoPlatformHandle platform_handle; +platform_handle.struct_size = sizeof(platform_handle); +MojoResult result = MojoUnwrapPlatformHandle(handle, &platform_handle); +int fd = (int)platform_handle.value; +``` + +The situation looks nearly identical for wrapping and unwrapping Windows handles +and Mach ports. + +### Wrapping Shared Buffer Handles + +Unlike other handle types, shared buffers have special meaning in Mojo, and it +may be desirable to wrap a native platform handle -- along with some extra +metadata -- such that be treated like a real Mojo shared buffer handle. +Conversely it can also be useful to unpack a Mojo shared buffer handle into +a native platform handle which references the buffer object. Both of these +things can be done using the `MojoWrapPlatformSharedBuffer` and +`MojoUnwrapPlatformSharedBuffer` APIs. + +On Windows, the wrapped platform handle must always be a Windows handle to +a file mapping object. + +On OS X, the wrapped platform handle must be a memory-object send right. + +On all other POSIX systems, the wrapped platform handle must be a file +descriptor for a shared memory object. + +## Signals & Watchers + +Message pipe and data pipe (producer and consumer) handles can change state in +ways that may be interesting to a Mojo API user. For example, you may wish to +know when a message pipe handle has messages available to be read or when its +peer has been closed. Such states are reflected by a fixed set of boolean +signals on each pipe handle. + +### Signals + +Every message pipe and data pipe handle maintains a notion of +**signaling state** which may be queried at any time. For example: + +``` c +MojoHandle a, b; +MojoCreateMessagePipe(NULL, &a, &b); + +MojoHandleSignalsState state; +MojoResult result = MojoQueryHandleSignalsState(a, &state); +``` + +The `MojoHandleSignalsState` structure exposes two fields: `satisfied_signals` +and `satisfiable_signals`. Both of these are bitmasks of the type +`MojoHandleSignals` (see [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h) +for more details.) + +The `satisfied_signals` bitmask indicates signals which were satisfied on the +handle at the time of the call, while the `satisfiable_signals` bitmask +indicates signals which were still possible to satisfy at the time of the call. +It is thus by definition always true that: + +``` c +(satisfied_signals | satisfiable_signals) == satisfiable_signals +``` + +In other words a signal obviously cannot be satisfied if it is no longer +satisfiable. Furthermore once a signal is unsatisfiable, *i.e.* is no longer +set in `sastisfiable_signals`, it can **never** become satisfiable again. + +To illustrate this more clearly, consider the message pipe created above. Both +ends of the pipe are still open and neither has been written to yet. Thus both +handles start out with the same signaling state: + +| Field | State | +|-----------------------|-------| +| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_WRITABLE` +| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_WRITABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED` + +Writing a message to handle `b` will eventually alter the signaling state of `a` +such that `MOJO_HANDLE_SIGNAL_READABLE` also becomes satisfied. If we were to +then close `b`, the signaling state of `a` would look like: + +| Field | State | +|-----------------------|-------| +| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED` +| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED` + +Note that even though `a`'s peer is known to be closed (hence making `a` +permanently unwritable) it remains readable because there's still an unread +received message waiting to be read from `a`. + +Finally if we read the last message from `a` its signaling state becomes: + +| Field | State | +|-----------------------|-------| +| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_PEER_CLOSED` +| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_PEER_CLOSED` + +and we know definitively that `a` can never be read from again. + +### Watching Signals + +The ability to query a handle's signaling state can be useful, but it's not +sufficient to support robust and efficient pipe usage. Mojo watchers empower +users with the ability to **watch** a handle's signaling state for interesting +changes and automatically invoke a notification handler in response. + +When a watcher is created it must be bound to a function pointer matching +the following signature, defined in +[//mojo/public/c/system/watcher.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/watcher.h): + +``` c +typedef void (*MojoWatcherNotificationCallback)( + uintptr_t context, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags); +``` + +The `context` argument corresponds to a specific handle being watched by the +watcher (read more below), and the remaining arguments provide details regarding +the specific reason for the notification. It's important to be aware that a +watcher's registered handler may be called **at any time** and +**on any thread**. + +It's also helpful to understand a bit about the mechanism by which the handler +can be invoked. Essentially, any Mojo C System API call may elicit a handle +state change of some kind. If such a change is relevant to conditions watched by +a watcher, and that watcher is in a state which allows it raise a corresponding +notification, its notification handler will be invoked synchronously some time +before the outermost System API call on the current thread's stack returns. + +Handle state changes can also occur as a result of incoming IPC from an external +process. If a pipe in the current process is connected to an endpoint in another +process and the internal Mojo system receives an incoming message bound for the +local endpoint, the arrival of that message will trigger a state change on the +receiving handle and may thus invoke one or more watchers' notification handlers +as a result. + +The `MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM` flag on the notification +handler's `flags` argument is used to indicate whether the handler was invoked +due to such an internal system IPC event (if the flag is set), or if it was +invoked synchronously due to some local API call (if the flag is unset.) +This distinction can be useful to make in certain cases to *e.g.* avoid +accidental reentrancy in user code. + +### Creating a Watcher + +Creating a watcher is simple: + +``` c + +void OnNotification(uintptr_t context, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + // ... +} + +MojoHandle w; +MojoResult result = MojoCreateWatcher(&OnNotification, &w); +``` + +Like all other `MojoHandle` types, watchers may be destroyed by closing them +with `MojoClose`. Unlike other `MojoHandle` types, watcher handles are **not** +transferrable across message pipes. + +In order for a watcher to be useful, it has to watch at least one handle. + +### Adding a Handle to a Watcher + +Any given watcher can watch any given (message or data pipe) handle for some set +of signaling conditions. A handle may be watched simultaneously by multiple +watchers, and a single watcher can watch multiple different handles +simultaneously. + +``` c +MojoHandle a, b; +MojoCreateMessagePipe(NULL, &a, &b); + +// Watch handle |a| for readability. +const uintptr_t context = 1234; +MojoResult result = MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context); +``` + +We've successfully instructed watcher `w` to begin watching pipe handle `a` for +readability. However, our recently created watcher is still in a **disarmed** +state, meaning that it will never fire a notification pertaining to this watched +signaling condition. It must be **armed** before that can happen. + +### Arming a Watcher + +In order for a watcher to invoke its notification handler in response to a +relevant signaling state change on a watched handle, it must first be armed. A +watcher may only be armed if none of its watched handles would elicit a +notification immediately once armed. + +In this case `a` is clearly not yet readable, so arming should succeed: + +``` c +MojoResult result = MojoArmWatcher(w, NULL, NULL, NULL, NULL); +``` + +Now we can write to `b` to make `a` readable: + +``` c +MojoWriteMessage(b, NULL, 0, NULL, 0, MOJO_WRITE_MESSAGE_NONE); +``` + +Eventually -- and in practice possibly before `MojoWriteMessage` even +returns -- this will cause `OnNotification` to be invoked on the calling thread +with the `context` value (*i.e.* 1234) that was given when the handle was added +to the watcher. + +The `result` parameter will be `MOJO_RESULT_OK` to indicate that the watched +signaling condition has been *satisfied*. If the watched condition had instead +become permanently *unsatisfiable* (*e.g.*, if `b` were instead closed), `result` +would instead indicate `MOJO_RESULT_FAILED_PRECONDITION`. + +**NOTE:** Immediately before a watcher decides to invoke its notification +handler, it automatically disarms itself to prevent another state change from +eliciting another notification. Therefore a watcher must be repeatedly rearmed +in order to continue dispatching signaling notifications. + +As noted above, arming a watcher may fail if any of the watched conditions for +a handle are already partially satisfied or fully unsatisfiable. In that case +the caller may provide buffers for `MojoArmWatcher` to store information about +a subset of the relevant watches which caused it to fail: + +``` c +// Provide some storage for information about watches that are already ready. +uint32_t num_ready_contexts = 4; +uintptr_t ready_contexts[4]; +MojoResult ready_results[4]; +struct MojoHandleSignalsStates ready_states[4]; +MojoResult result = MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states); +``` + +Because `a` is still readable this operation will fail with +`MOJO_RESULT_FAILED_PRECONDITION`. The input value of `num_ready_contexts` +informs `MojoArmWatcher` that it may store information regarding up to 4 watches +which currently prevent arming. In this case of course there is only one active +watch, so upon return we will see: + +* `num_ready_contexts` is `1`. +* `ready_contexts[0]` is `1234`. +* `ready_results[0]` is `MOJO_RESULT_OK` +* `ready_states[0]` is the last known signaling state of handle `a`. + +In other words the stored information mirrors what would have been the +notification handler's arguments if the watcher were allowed to arm and thus +notify immediately. + +### Cancelling a Watch + +There are three ways a watch can be cancelled: + +* The watched handle is closed +* The watcher handle is closed (in which case all of its watches are cancelled.) +* `MojoCancelWatch` is explicitly called for a given `context`. + +In the above example this means any of the following operations will cancel the +watch on `a`: + +``` c +// Close the watched handle... +MojoClose(a); + +// OR close the watcher handle... +MojoClose(w); + +// OR explicitly cancel. +MojoResult result = MojoCancelWatch(w, 1234); +``` + +In every case the watcher's notification handler is invoked for the cancelled +watch(es) regardless of whether or not the watcher is or was armed at the time. +The notification handler receives a `result` of `MOJO_RESULT_CANCELLED` for +these notifications, and this is guaranteed to be the final notification for any +given watch context. + +### Practical Watch Context Usage + +It is common and probably wise to treat a watch's `context` value as an opaque +pointer to some thread-safe state associated in some way with the handle being +watched. Here's a small example which uses a single watcher to watch both ends +of a message pipe and accumulate a count of messages received at each end. + +``` c +// NOTE: For the sake of simplicity this example code is not in fact +// thread-safe. As long as there's only one thread running in the process and +// no external process connections, this is fine. + +struct WatchedHandleState { + MojoHandle watcher; + MojoHandle handle; + int message_count; +}; + +void OnNotification(uintptr_t context, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + struct WatchedHandleState* state = (struct WatchedHandleState*)(context); + MojoResult rv; + + if (result == MOJO_RESULT_CANCELLED) { + // Cancellation is always the last notification and is guaranteed to + // eventually happen for every context, assuming no handles are leaked. We + // treat this as an opportunity to free the WatchedHandleState. + free(state); + return; + } + + if (result == MOJO_RESULT_FAILED_PRECONDITION) { + // No longer readable, i.e. the other handle must have been closed. Better + // cancel. Note that we could also just call MojoClose(state->watcher) here + // since we know |context| is its only registered watch. + MojoCancelWatch(state->watcher, context); + return; + } + + // This is the only handle watched by the watcher, so as long as we can't arm + // the watcher we know something's up with this handle. Try to read messages + // until we can successfully arm again or something goes terribly wrong. + while (MojoArmWatcher(state->watcher, NULL, NULL, NULL, NULL) == + MOJO_RESULT_FAILED_PRECONDITION) { + rv = MojoReadMessageNew(state->handle, NULL, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD); + if (rv == MOJO_RESULT_OK) { + state->message_count++; + } else if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + MojoCancelWatch(state->watcher, context); + return; + } + } +} + +MojoHandle a, b; +MojoCreateMessagePipe(NULL, &a, &b); + +MojoHandle a_watcher, b_watcher; +MojoCreateWatcher(&OnNotification, &a_watcher); +MojoCreateWatcher(&OnNotification, &b_watcher) + +struct WatchedHandleState* a_state = malloc(sizeof(struct WatchedHandleState)); +a_state->watcher = a_watcher; +a_state->handle = a; +a_state->message_count = 0; + +struct WatchedHandleState* b_state = malloc(sizeof(struct WatchedHandleState)); +b_state->watcher = b_watcher; +b_state->handle = b; +b_state->message_count = 0; + +MojoWatch(a_watcher, a, MOJO_HANDLE_SIGNAL_READABLE, (uintptr_t)a_state); +MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE, (uintptr_t)b_state); + +MojoArmWatcher(a_watcher, NULL, NULL, NULL, NULL); +MojoArmWatcher(b_watcher, NULL, NULL, NULL, NULL); +``` + +Now any writes to `a` will increment `message_count` in `b_state`, and any +writes to `b` will increment `message_count` in `a_state`. + +If either `a` or `b` is closed, both watches will be cancelled - one because +watch cancellation is implicit in handle closure, and the other because its +watcher will eventually detect that the handle is no longer readable. diff --git a/mojo/public/c/system/buffer.h b/mojo/public/c/system/buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..285e0d7b03684d42a49affc862b08e8b75f05eed --- /dev/null +++ b/mojo/public/c/system/buffer.h @@ -0,0 +1,188 @@ +// Copyright 2014 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. + +// This file contains types/constants and functions specific to shared buffers. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_BUFFER_H_ +#define MOJO_PUBLIC_C_SYSTEM_BUFFER_H_ + +#include + +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +// |MojoCreateSharedBufferOptions|: Used to specify creation parameters for a +// shared buffer to |MojoCreateSharedBuffer()|. +// +// |uint32_t struct_size|: Set to the size of the +// |MojoCreateSharedBufferOptions| struct. (Used to allow for future +// extensions.) +// +// |MojoCreateSharedBufferOptionsFlags flags|: Reserved for future use. +// |MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE|: No flags; default mode. + +typedef uint32_t MojoCreateSharedBufferOptionsFlags; + +#ifdef __cplusplus +const MojoCreateSharedBufferOptionsFlags + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE = 0; +#else +#define MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE \ + ((MojoCreateSharedBufferOptionsFlags)0) +#endif + +MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment"); +struct MOJO_ALIGNAS(8) MojoCreateSharedBufferOptions { + uint32_t struct_size; + MojoCreateSharedBufferOptionsFlags flags; +}; +MOJO_STATIC_ASSERT(sizeof(MojoCreateSharedBufferOptions) == 8, + "MojoCreateSharedBufferOptions has wrong size"); + +// |MojoDuplicateBufferHandleOptions|: Used to specify parameters in duplicating +// access to a shared buffer to |MojoDuplicateBufferHandle()|. +// +// |uint32_t struct_size|: Set to the size of the +// |MojoDuplicateBufferHandleOptions| struct. (Used to allow for future +// extensions.) +// +// |MojoDuplicateBufferHandleOptionsFlags flags|: Flags to control the +// behavior of |MojoDuplicateBufferHandle()|. May be some combination of +// the following: +// +// |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE|: No flags; default +// mode. +// |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY|: The duplicate +// shared buffer can only be mapped read-only. A read-only duplicate +// may only be created before any handles to the buffer are passed +// over a message pipe. + +typedef uint32_t MojoDuplicateBufferHandleOptionsFlags; + +#ifdef __cplusplus +const MojoDuplicateBufferHandleOptionsFlags + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE = 0; +const MojoDuplicateBufferHandleOptionsFlags + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY = 1 << 0; +#else +#define MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE \ + ((MojoDuplicateBufferHandleOptionsFlags)0) +#define MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY \ + ((MojoDuplicateBufferHandleOptionsFlags)1 << 0) +#endif + +struct MojoDuplicateBufferHandleOptions { + uint32_t struct_size; + MojoDuplicateBufferHandleOptionsFlags flags; +}; +MOJO_STATIC_ASSERT(sizeof(MojoDuplicateBufferHandleOptions) == 8, + "MojoDuplicateBufferHandleOptions has wrong size"); + +// |MojoMapBufferFlags|: Used to specify different modes to |MojoMapBuffer()|. +// |MOJO_MAP_BUFFER_FLAG_NONE| - No flags; default mode. + +typedef uint32_t MojoMapBufferFlags; + +#ifdef __cplusplus +const MojoMapBufferFlags MOJO_MAP_BUFFER_FLAG_NONE = 0; +#else +#define MOJO_MAP_BUFFER_FLAG_NONE ((MojoMapBufferFlags)0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: See the comment in functions.h about the meaning of the "optional" +// label for pointer parameters. + +// Creates a buffer of size |num_bytes| bytes that can be shared between +// processes. The returned handle may be duplicated any number of times by +// |MojoDuplicateBufferHandle()|. +// +// To access the buffer's storage, one must call |MojoMapBuffer()|. +// +// |options| may be set to null for a shared buffer with the default options. +// +// On success, |*shared_buffer_handle| will be set to the handle for the shared +// buffer. On failure it is not modified. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |*options| is invalid). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has +// been reached (e.g., if the requested size was too large, or if the +// maximum number of handles was exceeded). +// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateSharedBuffer( + const struct MojoCreateSharedBufferOptions* options, // Optional. + uint64_t num_bytes, // In. + MojoHandle* shared_buffer_handle); // Out. + +// Duplicates the handle |buffer_handle| as a new shared buffer handle. On +// success this returns the new handle in |*new_buffer_handle|. A shared buffer +// remains allocated as long as there is at least one shared buffer handle +// referencing it in at least one process in the system. +// +// |options| may be set to null to duplicate the buffer handle with the default +// options. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |buffer_handle| is not a valid buffer handle or |*options| is invalid). +// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|. +MOJO_SYSTEM_EXPORT MojoResult MojoDuplicateBufferHandle( + MojoHandle buffer_handle, + const struct MojoDuplicateBufferHandleOptions* options, // Optional. + MojoHandle* new_buffer_handle); // Out. + +// Maps the part (at offset |offset| of length |num_bytes|) of the buffer given +// by |buffer_handle| into memory, with options specified by |flags|. |offset + +// num_bytes| must be less than or equal to the size of the buffer. On success, +// |*buffer| points to memory with the requested part of the buffer. On +// failure |*buffer| it is not modified. +// +// A single buffer handle may have multiple active mappings The permissions +// (e.g., writable or executable) of the returned memory depend on th +// properties of the buffer and properties attached to the buffer handle, as +// well as |flags|. +// +// A mapped buffer must eventually be unmapped by calling |MojoUnmapBuffer()| +// with the value of |*buffer| returned by this function. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |buffer_handle| is not a valid buffer handle or the range specified by +// |offset| and |num_bytes| is not valid). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if the mapping operation itself failed +// (e.g., due to not having appropriate address space available). +MOJO_SYSTEM_EXPORT MojoResult MojoMapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, // Out. + MojoMapBufferFlags flags); + +// Unmaps a buffer pointer that was mapped by |MojoMapBuffer()|. |buffer| must +// have been the result of |MojoMapBuffer()| (not some other pointer inside +// the mapped memory), and the entire mapping will be removed. +// +// A mapping may only be unmapped once. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if |buffer| is invalid (e.g., is not the +// result of |MojoMapBuffer()| or has already been unmapped). +MOJO_SYSTEM_EXPORT MojoResult MojoUnmapBuffer(void* buffer); // In. + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_BUFFER_H_ diff --git a/mojo/public/c/system/core.h b/mojo/public/c/system/core.h new file mode 100644 index 0000000000000000000000000000000000000000..03c0652a574302524e103bc83cb3c5408c9e4b91 --- /dev/null +++ b/mojo/public/c/system/core.h @@ -0,0 +1,22 @@ +// Copyright 2014 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. + +// This is a catch-all header that includes everything. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_CORE_H_ +#define MOJO_PUBLIC_C_SYSTEM_CORE_H_ + +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/platform_handle.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/c/system/watcher.h" + +#endif // MOJO_PUBLIC_C_SYSTEM_CORE_H_ diff --git a/mojo/public/c/system/data_pipe.h b/mojo/public/c/system/data_pipe.h new file mode 100644 index 0000000000000000000000000000000000000000..f51e36cb2e02199cfde2b4789763ce5536f8233f --- /dev/null +++ b/mojo/public/c/system/data_pipe.h @@ -0,0 +1,344 @@ +// Copyright 2014 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. + +// This file contains types/constants and functions specific to data pipes. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_ +#define MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_ + +#include + +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +// |MojoCreateDataPipeOptions|: Used to specify creation parameters for a data +// pipe to |MojoCreateDataPipe()|. +// +// |uint32_t struct_size|: Set to the size of the |MojoCreateDataPipeOptions| +// struct. (Used to allow for future extensions.) +// +// |MojoCreateDataPipeOptionsFlags flags|: Used to specify different modes of +// operation. May be some combination of the following values: +// +// |MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode. +// +// |uint32_t element_num_bytes|: The size of an element, in bytes. All +// transactions and buffers will consist of an integral number of +// elements. Must be nonzero. +// +// |uint32_t capacity_num_bytes|: The capacity of the data pipe, in number of +// bytes; must be a multiple of |element_num_bytes|. The data pipe will +// always be able to queue AT LEAST this much data. Set to zero to opt for +// a system-dependent automatically-calculated capacity (which will always +// be at least one element). + +typedef uint32_t MojoCreateDataPipeOptionsFlags; + +#ifdef __cplusplus +const MojoCreateDataPipeOptionsFlags MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE = + 0; +#else +#define MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE \ + ((MojoCreateDataPipeOptionsFlags)0) +#endif + +MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment"); +struct MOJO_ALIGNAS(8) MojoCreateDataPipeOptions { + MOJO_ALIGNAS(4) uint32_t struct_size; + MOJO_ALIGNAS(4) MojoCreateDataPipeOptionsFlags flags; + MOJO_ALIGNAS(4) uint32_t element_num_bytes; + MOJO_ALIGNAS(4) uint32_t capacity_num_bytes; +}; +MOJO_STATIC_ASSERT(sizeof(MojoCreateDataPipeOptions) == 16, + "MojoCreateDataPipeOptions has wrong size"); + +// |MojoWriteDataFlags|: Used to specify different modes to |MojoWriteData()| +// and |MojoBeginWriteData()|. May be some combination of the following values: +// +// |MOJO_WRITE_DATA_FLAG_NONE| - No flags; default mode. +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| - Write either all the elements +// requested or none of them. + +typedef uint32_t MojoWriteDataFlags; + +#ifdef __cplusplus +const MojoWriteDataFlags MOJO_WRITE_DATA_FLAG_NONE = 0; +const MojoWriteDataFlags MOJO_WRITE_DATA_FLAG_ALL_OR_NONE = 1 << 0; +#else +#define MOJO_WRITE_DATA_FLAG_NONE ((MojoWriteDataFlags)0) +#define MOJO_WRITE_DATA_FLAG_ALL_OR_NONE ((MojoWriteDataFlags)1 << 0) +#endif + +// |MojoReadDataFlags|: Used to specify different modes to |MojoReadData()| and +// |MojoBeginReadData()|. May be some combination of the following values: +// +// |MOJO_READ_DATA_FLAG_NONE| - No flags; default mode. +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| - Read (or discard) either the requested +// number of elements or none. NOTE: This flag is not currently supported +// by |MojoBeginReadData()|. +// |MOJO_READ_DATA_FLAG_DISCARD| - Discard (up to) the requested number of +// elements. +// |MOJO_READ_DATA_FLAG_QUERY| - Query the number of elements available to +// read. For use with |MojoReadData()| only. Mutually exclusive with +// |MOJO_READ_DATA_FLAG_DISCARD|, and |MOJO_READ_DATA_FLAG_ALL_OR_NONE| +// is ignored if this flag is set. +// |MOJO_READ_DATA_FLAG_PEEK| - Read elements without removing them. For use +// with |MojoReadData()| only. Mutually exclusive with +// |MOJO_READ_DATA_FLAG_DISCARD| and |MOJO_READ_DATA_FLAG_QUERY|. + +typedef uint32_t MojoReadDataFlags; + +#ifdef __cplusplus +const MojoReadDataFlags MOJO_READ_DATA_FLAG_NONE = 0; +const MojoReadDataFlags MOJO_READ_DATA_FLAG_ALL_OR_NONE = 1 << 0; +const MojoReadDataFlags MOJO_READ_DATA_FLAG_DISCARD = 1 << 1; +const MojoReadDataFlags MOJO_READ_DATA_FLAG_QUERY = 1 << 2; +const MojoReadDataFlags MOJO_READ_DATA_FLAG_PEEK = 1 << 3; +#else +#define MOJO_READ_DATA_FLAG_NONE ((MojoReadDataFlags)0) +#define MOJO_READ_DATA_FLAG_ALL_OR_NONE ((MojoReadDataFlags)1 << 0) +#define MOJO_READ_DATA_FLAG_DISCARD ((MojoReadDataFlags)1 << 1) +#define MOJO_READ_DATA_FLAG_QUERY ((MojoReadDataFlags)1 << 2) +#define MOJO_READ_DATA_FLAG_PEEK ((MojoReadDataFlags)1 << 3) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: See the comment in functions.h about the meaning of the "optional" +// label for pointer parameters. + +// Creates a data pipe, which is a unidirectional communication channel for +// unframed data. Data must be read and written in multiples of discrete +// discrete elements of size given in |options|. +// +// See |MojoCreateDataPipeOptions| for a description of the different options +// available for data pipes. +// +// |options| may be set to null for a data pipe with the default options (which +// will have an element size of one byte and have some system-dependent +// capacity). +// +// On success, |*data_pipe_producer_handle| will be set to the handle for the +// producer and |*data_pipe_consumer_handle| will be set to the handle for the +// consumer. On failure they are not modified. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |*options| is invalid). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has +// been reached (e.g., if the requested capacity was too large, or if the +// maximum number of handles was exceeded). +// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateDataPipe( + const struct MojoCreateDataPipeOptions* options, // Optional. + MojoHandle* data_pipe_producer_handle, // Out. + MojoHandle* data_pipe_consumer_handle); // Out. + +// Writes the data pipe producer given by |data_pipe_producer_handle|. +// +// |elements| points to data of size |*num_bytes|; |*num_bytes| must be a +// multiple of the data pipe's element size. If +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| is set in |flags|, either all the data +// is written (if enough write capacity is available) or none is. +// +// On success |*num_bytes| is set to the amount of data that was actually +// written. On failure it is unmodified. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_producer_dispatcher| is not a handle to a data pipe +// producer or |*num_bytes| is not a multiple of the data pipe's element +// size.) +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been +// closed. +// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set and the required amount of data +// (specified by |*num_bytes|) could not be written. +// |MOJO_RESULT_BUSY| if there is a two-phase write ongoing with +// |data_pipe_producer_handle| (i.e., |MojoBeginWriteData()| has been +// called, but not yet the matching |MojoEndWriteData()|). +// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be written (and the +// consumer is still open) and |flags| does *not* have +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set. +MOJO_SYSTEM_EXPORT MojoResult + MojoWriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_bytes, // In/out. + MojoWriteDataFlags flags); + +// Begins a two-phase write to the data pipe producer given by +// |data_pipe_producer_handle|. On success |*buffer| will be a pointer to which +// the caller can write up to |*buffer_num_bytes| bytes of data. +// +// During a two-phase write, |data_pipe_producer_handle| is *not* writable. +// If another caller tries to write to it by calling |MojoWriteData()| or +// |MojoBeginWriteData()|, their request will fail with |MOJO_RESULT_BUSY|. +// +// If |MojoBeginWriteData()| returns MOJO_RESULT_OK and once the caller has +// finished writing data to |*buffer|, |MojoEndWriteData()| must be called to +// indicate the amount of data actually written and to complete the two-phase +// write operation. |MojoEndWriteData()| need not be called when +// |MojoBeginWriteData()| fails. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_producer_handle| is not a handle to a data pipe producer or +// flags has |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set. +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been +// closed. +// |MOJO_RESULT_BUSY| if there is already a two-phase write ongoing with +// |data_pipe_producer_handle| (i.e., |MojoBeginWriteData()| has been +// called, but not yet the matching |MojoEndWriteData()|). +// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be written (and the +// consumer is still open). +MOJO_SYSTEM_EXPORT MojoResult + MojoBeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, // Out. + uint32_t* buffer_num_bytes, // In/out. + MojoWriteDataFlags flags); + +// Ends a two-phase write that was previously initiated by +// |MojoBeginWriteData()| for the same |data_pipe_producer_handle|. +// +// |num_bytes_written| must indicate the number of bytes actually written into +// the two-phase write buffer. It must be less than or equal to the value of +// |*buffer_num_bytes| output by |MojoBeginWriteData()|, and it must be a +// multiple of the data pipe's element size. +// +// On failure, the two-phase write (if any) is ended (so the handle may become +// writable again if there's space available) but no data written to |*buffer| +// is "put into" the data pipe. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_producer_handle| is not a handle to a data pipe producer or +// |num_bytes_written| is invalid (greater than the maximum value provided +// by |MojoBeginWriteData()| or not a multiple of the element size). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer is not in a +// two-phase write (e.g., |MojoBeginWriteData()| was not called or +// |MojoEndWriteData()| has already been called). +MOJO_SYSTEM_EXPORT MojoResult + MojoEndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_bytes_written); + +// Reads data from the data pipe consumer given by |data_pipe_consumer_handle|. +// May also be used to discard data or query the amount of data available. +// +// If |flags| has neither |MOJO_READ_DATA_FLAG_DISCARD| nor +// |MOJO_READ_DATA_FLAG_QUERY| set, this tries to read up to |*num_bytes| (which +// must be a multiple of the data pipe's element size) bytes of data to +// |elements| and set |*num_bytes| to the amount actually read. If flags has +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set, it will either read exactly +// |*num_bytes| bytes of data or none. Additionally, if flags has +// |MOJO_READ_DATA_FLAG_PEEK| set, the data read will remain in the pipe and be +// available to future reads. +// +// If flags has |MOJO_READ_DATA_FLAG_DISCARD| set, it discards up to +// |*num_bytes| (which again must be a multiple of the element size) bytes of +// data, setting |*num_bytes| to the amount actually discarded. If flags has +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE|, it will either discard exactly +// |*num_bytes| bytes of data or none. In this case, |MOJO_READ_DATA_FLAG_QUERY| +// must not be set, and |elements| is ignored (and should typically be set to +// null). +// +// If flags has |MOJO_READ_DATA_FLAG_QUERY| set, it queries the amount of data +// available, setting |*num_bytes| to the number of bytes available. In this +// case, |MOJO_READ_DATA_FLAG_DISCARD| must not be set, and +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| is ignored, as are |elements| and the input +// value of |*num_bytes|. +// +// Returns: +// |MOJO_RESULT_OK| on success (see above for a description of the different +// operations). +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_consumer_handle| is invalid, the combination of flags in +// |flags| is invalid, etc.). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer handle has been +// closed and data (or the required amount of data) was not available to +// be read or discarded. +// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has |MOJO_READ_DATA_FLAG_ALL_OR_NONE| +// set and the required amount of data is not available to be read or +// discarded (and the producer is still open). +// |MOJO_RESULT_BUSY| if there is a two-phase read ongoing with +// |data_pipe_consumer_handle| (i.e., |MojoBeginReadData()| has been +// called, but not yet the matching |MojoEndReadData()|). +// |MOJO_RESULT_SHOULD_WAIT| if there is no data to be read or discarded (and +// the producer is still open) and |flags| does *not* have +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set. +MOJO_SYSTEM_EXPORT MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle, + void* elements, // Out. + uint32_t* num_bytes, // In/out. + MojoReadDataFlags flags); + +// Begins a two-phase read from the data pipe consumer given by +// |data_pipe_consumer_handle|. On success, |*buffer| will be a pointer from +// which the caller can read up to |*buffer_num_bytes| bytes of data. +// +// During a two-phase read, |data_pipe_consumer_handle| is *not* readable. +// If another caller tries to read from it by calling |MojoReadData()| or +// |MojoBeginReadData()|, their request will fail with |MOJO_RESULT_BUSY|. +// +// Once the caller has finished reading data from |*buffer|, |MojoEndReadData()| +// must be called to indicate the number of bytes read and to complete the +// two-phase read operation. +// +// |flags| must not have |MOJO_READ_DATA_FLAG_DISCARD|, +// |MOJO_READ_DATA_FLAG_QUERY|, |MOJO_READ_DATA_FLAG_PEEK|, or +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_consumer_handle| is not a handle to a data pipe consumer, +// or |flags| has invalid flags set.) +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer handle has been +// closed. +// |MOJO_RESULT_BUSY| if there is already a two-phase read ongoing with +// |data_pipe_consumer_handle| (i.e., |MojoBeginReadData()| has been +// called, but not yet the matching |MojoEndReadData()|). +// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be read (and the +// producer is still open). +MOJO_SYSTEM_EXPORT MojoResult + MojoBeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, // Out. + uint32_t* buffer_num_bytes, // In/out. + MojoReadDataFlags flags); + +// Ends a two-phase read from the data pipe consumer given by +// |data_pipe_consumer_handle| that was begun by a call to |MojoBeginReadData()| +// on the same handle. |num_bytes_read| should indicate the amount of data +// actually read; it must be less than or equal to the value of +// |*buffer_num_bytes| output by |MojoBeginReadData()| and must be a multiple of +// the element size. +// +// On failure, the two-phase read (if any) is ended (so the handle may become +// readable again) but no data is "removed" from the data pipe. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_consumer_handle| is not a handle to a data pipe consumer or +// |num_bytes_written| is greater than the maximum value provided by +// |MojoBeginReadData()| or not a multiple of the element size). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer is not in a +// two-phase read (e.g., |MojoBeginReadData()| was not called or +// |MojoEndReadData()| has already been called). +MOJO_SYSTEM_EXPORT MojoResult + MojoEndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_bytes_read); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_ diff --git a/mojo/public/c/system/functions.h b/mojo/public/c/system/functions.h new file mode 100644 index 0000000000000000000000000000000000000000..d0656c67fc4695c19416a184260e3d946845fb1a --- /dev/null +++ b/mojo/public/c/system/functions.h @@ -0,0 +1,78 @@ +// Copyright 2014 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. + +// This file contains basic functions common to different Mojo system APIs. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_ +#define MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_ + +#include +#include + +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: Pointer parameters that are labelled "optional" may be null (at least +// under some circumstances). Non-const pointer parameters are also labeled +// "in", "out", or "in/out", to indicate how they are used. (Note that how/if +// such a parameter is used may depend on other parameters or the requested +// operation's success/failure. E.g., a separate |flags| parameter may control +// whether a given "in/out" parameter is used for input, output, or both.) + +// Returns the time, in microseconds, since some undefined point in the past. +// The values are only meaningful relative to other values that were obtained +// from the same device without an intervening system restart. Such values are +// guaranteed to be monotonically non-decreasing with the passage of real time. +// Although the units are microseconds, the resolution of the clock may vary and +// is typically in the range of ~1-15 ms. +MOJO_SYSTEM_EXPORT MojoTimeTicks MojoGetTimeTicksNow(void); + +// Closes the given |handle|. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle. +// +// Concurrent operations on |handle| may succeed (or fail as usual) if they +// happen before the close, be cancelled with result |MOJO_RESULT_CANCELLED| if +// they properly overlap (this is likely the case with watchers), or fail with +// |MOJO_RESULT_INVALID_ARGUMENT| if they happen after. +MOJO_SYSTEM_EXPORT MojoResult MojoClose(MojoHandle handle); + +// Queries the last known signals state of a handle. +// +// Note that no guarantees can be made about the accuracy of the returned +// signals state by the time this returns, as other threads in the system may +// change the handle's state at any time. Use with appropriate discretion. +// +// Returns: +// |MOJO_RESULT_OK| on success. |*signals_state| is populated with the +// last known signals state of |handle|. +// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle or +// |signals_state| is null. +MOJO_SYSTEM_EXPORT MojoResult +MojoQueryHandleSignalsState(MojoHandle handle, + struct MojoHandleSignalsState* signals_state); + +// Retrieves system properties. See the documentation for |MojoPropertyType| for +// supported property types and their corresponding output value type. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if |type| is not recognized. In this case, +// |value| is untouched. +MOJO_SYSTEM_EXPORT MojoResult MojoGetProperty(MojoPropertyType type, + void* value); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_ diff --git a/mojo/public/c/system/macros.h b/mojo/public/c/system/macros.h new file mode 100644 index 0000000000000000000000000000000000000000..917c69cc1569a183beb4d034e65759b3b83d3cfb --- /dev/null +++ b/mojo/public/c/system/macros.h @@ -0,0 +1,49 @@ +// Copyright 2014 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 MOJO_PUBLIC_C_SYSTEM_MACROS_H_ +#define MOJO_PUBLIC_C_SYSTEM_MACROS_H_ + +#include + +// Assert things at compile time. (|msg| should be a valid identifier name.) +// This macro is currently C++-only, but we want to use it in the C core.h. +// Use like: +// MOJO_STATIC_ASSERT(sizeof(Foo) == 12, "Foo has invalid size"); +#if defined(__cplusplus) +#define MOJO_STATIC_ASSERT(expr, msg) static_assert(expr, msg) +#else +#define MOJO_STATIC_ASSERT(expr, msg) +#endif + +// Like the C++11 |alignof| operator. +#if __cplusplus >= 201103L +#define MOJO_ALIGNOF(type) alignof(type) +#elif defined(__GNUC__) +#define MOJO_ALIGNOF(type) __alignof__(type) +#elif defined(_MSC_VER) +// The use of |sizeof| is to work around a bug in MSVC 2010 (see +// http://goo.gl/isH0C; supposedly fixed since then). +#define MOJO_ALIGNOF(type) (sizeof(type) - sizeof(type) + __alignof(type)) +#else +#error "Please define MOJO_ALIGNOF() for your compiler." +#endif + +// Specify the alignment of a |struct|, etc. +// Use like: +// struct MOJO_ALIGNAS(8) Foo { ... }; +// Unlike the C++11 |alignas()|, |alignment| must be an integer. It may not be a +// type, nor can it be an expression like |MOJO_ALIGNOF(type)| (due to the +// non-C++11 MSVS version). +#if __cplusplus >= 201103L +#define MOJO_ALIGNAS(alignment) alignas(alignment) +#elif defined(__GNUC__) +#define MOJO_ALIGNAS(alignment) __attribute__((aligned(alignment))) +#elif defined(_MSC_VER) +#define MOJO_ALIGNAS(alignment) __declspec(align(alignment)) +#else +#error "Please define MOJO_ALIGNAS() for your compiler." +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_MACROS_H_ diff --git a/mojo/public/c/system/message_pipe.h b/mojo/public/c/system/message_pipe.h new file mode 100644 index 0000000000000000000000000000000000000000..b759bc73db4927826ec684ae58fd0947b6c82408 --- /dev/null +++ b/mojo/public/c/system/message_pipe.h @@ -0,0 +1,341 @@ +// Copyright 2014 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. + +// This file contains types/constants and functions specific to message pipes. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_ +#define MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_ + +#include + +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +// |MojoMessageHandle|: Used to refer to message objects created by +// |MojoAllocMessage()| and transferred by |MojoWriteMessageNew()| or +// |MojoReadMessageNew()|. + +typedef uintptr_t MojoMessageHandle; + +#ifdef __cplusplus +const MojoMessageHandle MOJO_MESSAGE_HANDLE_INVALID = 0; +#else +#define MOJO_MESSAGE_HANDLE_INVALID ((MojoMessageHandle)0) +#endif + +// |MojoCreateMessagePipeOptions|: Used to specify creation parameters for a +// message pipe to |MojoCreateMessagePipe()|. +// |uint32_t struct_size|: Set to the size of the +// |MojoCreateMessagePipeOptions| struct. (Used to allow for future +// extensions.) +// |MojoCreateMessagePipeOptionsFlags flags|: Used to specify different modes +// of operation. +// |MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode. + +typedef uint32_t MojoCreateMessagePipeOptionsFlags; + +#ifdef __cplusplus +const MojoCreateMessagePipeOptionsFlags + MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE = 0; +#else +#define MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE \ + ((MojoCreateMessagePipeOptionsFlags)0) +#endif + +MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment"); +struct MOJO_ALIGNAS(8) MojoCreateMessagePipeOptions { + uint32_t struct_size; + MojoCreateMessagePipeOptionsFlags flags; +}; +MOJO_STATIC_ASSERT(sizeof(MojoCreateMessagePipeOptions) == 8, + "MojoCreateMessagePipeOptions has wrong size"); + +// |MojoWriteMessageFlags|: Used to specify different modes to +// |MojoWriteMessage()|. +// |MOJO_WRITE_MESSAGE_FLAG_NONE| - No flags; default mode. + +typedef uint32_t MojoWriteMessageFlags; + +#ifdef __cplusplus +const MojoWriteMessageFlags MOJO_WRITE_MESSAGE_FLAG_NONE = 0; +#else +#define MOJO_WRITE_MESSAGE_FLAG_NONE ((MojoWriteMessageFlags)0) +#endif + +// |MojoReadMessageFlags|: Used to specify different modes to +// |MojoReadMessage()|. +// |MOJO_READ_MESSAGE_FLAG_NONE| - No flags; default mode. +// |MOJO_READ_MESSAGE_FLAG_MAY_DISCARD| - If the message is unable to be read +// for whatever reason (e.g., the caller-supplied buffer is too small), +// discard the message (i.e., simply dequeue it). + +typedef uint32_t MojoReadMessageFlags; + +#ifdef __cplusplus +const MojoReadMessageFlags MOJO_READ_MESSAGE_FLAG_NONE = 0; +const MojoReadMessageFlags MOJO_READ_MESSAGE_FLAG_MAY_DISCARD = 1 << 0; +#else +#define MOJO_READ_MESSAGE_FLAG_NONE ((MojoReadMessageFlags)0) +#define MOJO_READ_MESSAGE_FLAG_MAY_DISCARD ((MojoReadMessageFlags)1 << 0) +#endif + +// |MojoAllocMessageFlags|: Used to specify different options for +// |MojoAllocMessage()|. +// |MOJO_ALLOC_MESSAGE_FLAG_NONE| - No flags; default mode. + +typedef uint32_t MojoAllocMessageFlags; + +#ifdef __cplusplus +const MojoAllocMessageFlags MOJO_ALLOC_MESSAGE_FLAG_NONE = 0; +#else +#define MOJO_ALLOC_MESSAGE_FLAG_NONE ((MojoAllocMessageFlags)0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: See the comment in functions.h about the meaning of the "optional" +// label for pointer parameters. + +// Creates a message pipe, which is a bidirectional communication channel for +// framed data (i.e., messages). Messages can contain plain data and/or Mojo +// handles. +// +// |options| may be set to null for a message pipe with the default options. +// +// On success, |*message_pipe_handle0| and |*message_pipe_handle1| are set to +// handles for the two endpoints (ports) for the message pipe. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |*options| is invalid). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has +// been reached. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateMessagePipe( + const struct MojoCreateMessagePipeOptions* options, // Optional. + MojoHandle* message_pipe_handle0, // Out. + MojoHandle* message_pipe_handle1); // Out. + +// Writes a message to the message pipe endpoint given by |message_pipe_handle|, +// with message data specified by |bytes| of size |num_bytes| and attached +// handles specified by |handles| of count |num_handles|, and options specified +// by |flags|. If there is no message data, |bytes| may be null, in which case +// |num_bytes| must be zero. If there are no attached handles, |handles| may be +// null, in which case |num_handles| must be zero. +// +// If handles are attached, the handles will no longer be valid (on success the +// receiver will receive equivalent, but logically different, handles). Handles +// to be sent should not be in simultaneous use (e.g., on another thread). +// +// Returns: +// |MOJO_RESULT_OK| on success (i.e., the message was enqueued). +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., if +// |message_pipe_handle| is not a valid handle, or some of the +// requirements above are not satisfied). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if some system limit has been reached, or +// the number of handles to send is too large (TODO(vtl): reconsider the +// latter case). +// |MOJO_RESULT_FAILED_PRECONDITION| if the other endpoint has been closed. +// Note that closing an endpoint is not necessarily synchronous (e.g., +// across processes), so this function may succeed even if the other +// endpoint has been closed (in which case the message would be dropped). +// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|. +// |MOJO_RESULT_BUSY| if some handle to be sent is currently in use. +// +// TODO(vtl): Add a notion of capacity for message pipes, and return +// |MOJO_RESULT_SHOULD_WAIT| if the message pipe is full. +MOJO_SYSTEM_EXPORT MojoResult + MojoWriteMessage(MojoHandle message_pipe_handle, + const void* bytes, // Optional. + uint32_t num_bytes, + const MojoHandle* handles, // Optional. + uint32_t num_handles, + MojoWriteMessageFlags flags); + +// Writes a message to the message pipe endpoint given by |message_pipe_handle|. +// +// |message|: A message object allocated by |MojoAllocMessage()|. Ownership of +// the message is passed into Mojo. +// +// Returns results corresponding to |MojoWriteMessage()| above. +MOJO_SYSTEM_EXPORT MojoResult + MojoWriteMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags); + +// Reads the next message from a message pipe, or indicates the size of the +// message if it cannot fit in the provided buffers. The message will be read +// in its entirety or not at all; if it is not, it will remain enqueued unless +// the |MOJO_READ_MESSAGE_FLAG_MAY_DISCARD| flag was passed. At most one +// message will be consumed from the queue, and the return value will indicate +// whether a message was successfully read. +// +// |num_bytes| and |num_handles| are optional in/out parameters that on input +// must be set to the sizes of the |bytes| and |handles| arrays, and on output +// will be set to the actual number of bytes or handles contained in the +// message (even if the message was not retrieved due to being too large). +// Either |num_bytes| or |num_handles| may be null if the message is not +// expected to contain the corresponding type of data, but such a call would +// fail with |MOJO_RESULT_RESOURCE_EXHAUSTED| if the message in fact did +// contain that type of data. +// +// |bytes| and |handles| will receive the contents of the message, if it is +// retrieved. Either or both may be null, in which case the corresponding size +// parameter(s) must also be set to zero or passed as null. +// +// Returns: +// |MOJO_RESULT_OK| on success (i.e., a message was actually read). +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid. +// |MOJO_RESULT_FAILED_PRECONDITION| if the other endpoint has been closed. +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if the message was too large to fit in the +// provided buffer(s). The message will have been left in the queue or +// discarded, depending on flags. +// |MOJO_RESULT_SHOULD_WAIT| if no message was available to be read. +// +// TODO(vtl): Reconsider the |MOJO_RESULT_RESOURCE_EXHAUSTED| error code; should +// distinguish this from the hitting-system-limits case. +MOJO_SYSTEM_EXPORT MojoResult + MojoReadMessage(MojoHandle message_pipe_handle, + void* bytes, // Optional out. + uint32_t* num_bytes, // Optional in/out. + MojoHandle* handles, // Optional out. + uint32_t* num_handles, // Optional in/out. + MojoReadMessageFlags flags); + +// Reads the next message from a message pipe and returns a message containing +// the message bytes. The returned message must eventually be freed using +// |MojoFreeMessage()|. +// +// Message payload can be accessed using |MojoGetMessageBuffer()|. +// +// |message_pipe_handle|, |num_bytes|, |handles|, |num_handles|, and |flags| +// correspond to their use in |MojoReadMessage()| above, with the +// exception that |num_bytes| is only an output argument. +// |message| must be non-null unless |MOJO_READ_MESSAGE_FLAG_MAY_DISCARD| is +// set in flags. +// +// Return values correspond to the return values for |MojoReadMessage()| above. +// On success (MOJO_RESULT_OK), |*message| will contain a handle to a message +// object which may be passed to |MojoGetMessageBuffer()|. The caller owns the +// message object and is responsible for freeing it via |MojoFreeMessage()|. +MOJO_SYSTEM_EXPORT MojoResult + MojoReadMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle* message, // Optional out. + uint32_t* num_bytes, // Optional out. + MojoHandle* handles, // Optional out. + uint32_t* num_handles, // Optional in/out. + MojoReadMessageFlags flags); + +// Fuses two message pipe endpoints together. Given two pipes: +// +// A <-> B and C <-> D +// +// Fusing handle B and handle C results in a single pipe: +// +// A <-> D +// +// Handles B and C are ALWAYS closed. Any unread messages at C will eventually +// be delivered to A, and any unread messages at B will eventually be delivered +// to D. +// +// NOTE: A handle may only be fused if it is an open message pipe handle which +// has not been written to. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_FAILED_PRECONDITION| if both handles were valid message pipe +// handles but could not be merged (e.g. one of them has been written to). +// |MOJO_INVALID_ARGUMENT| if either handle is not a fusable message pipe +// handle. +MOJO_SYSTEM_EXPORT MojoResult + MojoFuseMessagePipes(MojoHandle handle0, MojoHandle handle1); + +// Allocates a new message whose ownership may be passed to +// |MojoWriteMessageNew()|. Use |MojoGetMessageBuffer()| to retrieve the address +// of the mutable message payload. +// +// |num_bytes|: The size of the message payload in bytes. +// |handles|: An array of handles to transfer in the message. This takes +// ownership of and invalidates all contained handles. Must be null if and +// only if |num_handles| is 0. +// |num_handles|: The number of handles contained in |handles|. +// |flags|: Must be |MOJO_CREATE_MESSAGE_FLAG_NONE|. +// |message|: The address of a handle to be filled with the allocated message's +// handle. Must be non-null. +// +// Returns: +// |MOJO_RESULT_OK| if the message was successfully allocated. In this case +// |*message| will be populated with a handle to an allocated message +// with a buffer large enough to hold |num_bytes| contiguous bytes. +// |MOJO_RESULT_INVALID_ARGUMENT| if one or more handles in |handles| was +// invalid, or |handles| was null with a non-zero |num_handles|. +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if allocation failed because either +// |num_bytes| or |num_handles| exceeds an implementation-defined maximum. +// |MOJO_RESULT_BUSY| if one or more handles in |handles| cannot be sent at +// the time of this call. +// +// Only upon successful message allocation will all handles in |handles| be +// transferred into the message and invalidated. +MOJO_SYSTEM_EXPORT MojoResult +MojoAllocMessage(uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoAllocMessageFlags flags, + MojoMessageHandle* message); // Out + +// Frees a message allocated by |MojoAllocMessage()| or |MojoReadMessageNew()|. +// +// |message|: The message to free. This must correspond to a message previously +// allocated by |MojoAllocMessage()| or |MojoReadMessageNew()|. Note that if +// the message has already been passed to |MojoWriteMessageNew()| it should +// NOT also be freed with this API. +// +// Returns: +// |MOJO_RESULT_OK| if |message| was valid and has been freed. +// |MOJO_RESULT_INVALID_ARGUMENT| if |message| was not a valid message. +MOJO_SYSTEM_EXPORT MojoResult MojoFreeMessage(MojoMessageHandle message); + +// Retrieves the address of mutable message bytes for a message allocated by +// either |MojoAllocMessage()| or |MojoReadMessageNew()|. +// +// Returns: +// |MOJO_RESULT_OK| if |message| is a valid message object. |*buffer| will +// be updated to point to mutable message bytes. +// |MOJO_RESULT_INVALID_ARGUMENT| if |message| is not a valid message object. +// +// NOTE: A returned buffer address is always guaranteed to be 8-byte aligned. +MOJO_SYSTEM_EXPORT MojoResult MojoGetMessageBuffer(MojoMessageHandle message, + void** buffer); // Out + +// Notifies the system that a bad message was received on a message pipe, +// according to whatever criteria the caller chooses. This ultimately tries to +// notify the embedder about the bad message, and the embedder may enforce some +// policy for dealing with the source of the message (e.g. close the pipe, +// terminate, a process, etc.) The embedder may not be notified if the calling +// process has lost its connection to the source process. +// +// |message|: The message to report as bad. This must have come from a call to +// |MojoReadMessageNew()|. +// |error|: An error string which may provide the embedder with context when +// notified of this error. +// |error_num_bytes|: The length of |error| in bytes. +// +// Returns: +// |MOJO_RESULT_OK| if successful. +// |MOJO_RESULT_INVALID_ARGUMENT| if |message| is not a valid message. +MOJO_SYSTEM_EXPORT MojoResult +MojoNotifyBadMessage(MojoMessageHandle message, + const char* error, + size_t error_num_bytes); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_ diff --git a/mojo/public/c/system/platform_handle.h b/mojo/public/c/system/platform_handle.h new file mode 100644 index 0000000000000000000000000000000000000000..7449c2e7943471495991679359fa06e7a70591d1 --- /dev/null +++ b/mojo/public/c/system/platform_handle.h @@ -0,0 +1,191 @@ +// Copyright 2016 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. + +// This file contains types/functions and constants for platform handle wrapping +// and unwrapping APIs. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_PLATFORM_HANDLE_H_ +#define MOJO_PUBLIC_C_SYSTEM_PLATFORM_HANDLE_H_ + +#include + +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// |MojoPlatformHandleType|: A value indicating the specific type of platform +// handle encapsulated by a MojoPlatformHandle (see below.) This is stored +// in the MojoPlatformHandle's |type| field and determines how the |value| +// field is interpreted. +// +// |MOJO_PLATFORM_HANDLE_TYPE_INVALID| - An invalid platform handle. +// |MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR| - A file descriptor. Only valid +// on POSIX systems. +// |MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT| - A Mach port. Only valid on OS X. +// |MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE| - A Windows HANDLE value. Only +// valid on Windows. + +typedef uint32_t MojoPlatformHandleType; + +#ifdef __cplusplus +const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_INVALID = 0; +const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR = 1; +const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT = 2; +const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE = 3; +#else +#define MOJO_PLATFORM_HANDLE_TYPE_INVALID ((MojoPlatformHandleType)0) +#define MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR ((MojoPlatformHandleType)1) +#define MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT ((MojoPlatformHandleType)2) +#define MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE ((MojoPlatformHandleType)3) +#endif + +// |MojoPlatformHandle|: A handle to a native platform object. +// +// |uint32_t struct_size|: The size of this structure. Used for versioning +// to allow for future extensions. +// +// |MojoPlatformHandleType type|: The type of handle stored in |value|. +// +// |uint64_t value|: The value of this handle. Ignored if |type| is +// MOJO_PLATFORM_HANDLE_TYPE_INVALID. Otherwise the meaning of this +// value depends on the value of |type|. +// + +struct MOJO_ALIGNAS(8) MojoPlatformHandle { + uint32_t struct_size; + MojoPlatformHandleType type; + uint64_t value; +}; +MOJO_STATIC_ASSERT(sizeof(MojoPlatformHandle) == 16, + "MojoPlatformHandle has wrong size"); + +// |MojoPlatformSharedBufferHandleFlags|: Flags relevant to wrapped platform +// shared buffers. +// +// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_NONE| - No flags. +// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_READ_ONLY| - Indicates that the wrapped +// buffer handle may only be mapped for reading. + +typedef uint32_t MojoPlatformSharedBufferHandleFlags; + +#ifdef __cplusplus +const MojoPlatformSharedBufferHandleFlags +MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE = 0; + +const MojoPlatformSharedBufferHandleFlags +MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY = 1 << 0; +#else +#define MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE \ + ((MojoPlatformSharedBufferHandleFlags)0) + +#define MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY \ + ((MojoPlatformSharedBufferHandleFlags)1 << 0) +#endif + +// Wraps a native platform handle as a Mojo handle which can be transferred +// over a message pipe. Takes ownership of the underlying native platform +// object. +// +// |platform_handle|: The platform handle to wrap. +// +// Returns: +// |MOJO_RESULT_OK| if the handle was successfully wrapped. In this case +// |*mojo_handle| contains the Mojo handle of the wrapped object. +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if the system is out of handles. +// |MOJO_RESULT_INVALID_ARGUMENT| if |platform_handle| was not a valid +// platform handle. +// +// NOTE: It is not always possible to detect if |platform_handle| is valid, +// particularly when |platform_handle->type| is valid but +// |platform_handle->value| does not represent a valid platform object. +MOJO_SYSTEM_EXPORT MojoResult +MojoWrapPlatformHandle(const struct MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle); // Out + +// Unwraps a native platform handle from a Mojo handle. If this call succeeds, +// ownership of the underlying platform object is assumed by the caller. The +// The Mojo handle is always closed regardless of success or failure. +// +// |mojo_handle|: The Mojo handle from which to unwrap the native platform +// handle. +// +// Returns: +// |MOJO_RESULT_OK| if the handle was successfully unwrapped. In this case +// |*platform_handle| contains the unwrapped platform handle. +// |MOJO_RESULT_INVALID_ARGUMENT| if |mojo_handle| was not a valid Mojo +// handle wrapping a platform handle. +MOJO_SYSTEM_EXPORT MojoResult +MojoUnwrapPlatformHandle(MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle); // Out + +// Wraps a native platform shared buffer handle as a Mojo shared buffer handle +// which can be used exactly like a shared buffer handle created by +// |MojoCreateSharedBuffer()| or |MojoDuplicateBufferHandle()|. +// +// Takes ownership of the native platform shared buffer handle. +// +// |platform_handle|: The platform handle to wrap. Must be a native handle to a +// shared buffer object. +// |num_bytes|: The size of the shared buffer in bytes. +// |flags|: Flags which influence the treatment of the shared buffer object. See +// below. +// +// Flags: +// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE| indicates default behavior. +// No flags set. +// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY| indicates that the +// buffer handled to be wrapped may only be mapped as read-only. This +// flag does NOT change the access control of the buffer in any way. +// +// Returns: +// |MOJO_RESULT_OK| if the handle was successfully wrapped. In this case +// |*mojo_handle| contains a Mojo shared buffer handle. +// |MOJO_RESULT_INVALID_ARGUMENT| if |platform_handle| was not a valid +// platform shared buffer handle. +MOJO_SYSTEM_EXPORT MojoResult +MojoWrapPlatformSharedBufferHandle( + const struct MojoPlatformHandle* platform_handle, + size_t num_bytes, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle); // Out + +// Unwraps a native platform shared buffer handle from a Mojo shared buffer +// handle. If this call succeeds, ownership of the underlying shared buffer +// object is assumed by the caller. +// +// The Mojo handle is always closed regardless of success or failure. +// +// |mojo_handle|: The Mojo shared buffer handle to unwrap. +// +// |platform_handle|, |num_bytes| and |flags| are used to receive output values +// and MUST always be non-null. +// +// Returns: +// |MOJO_RESULT_OK| if the handle was successfully unwrapped. In this case +// |*platform_handle| contains a platform shared buffer handle, +// |*num_bytes| contains the size of the shared buffer object, and +// |*flags| indicates flags relevant to the wrapped buffer (see below). +// |MOJO_RESULT_INVALID_ARGUMENT| if |mojo_handle| is not a valid Mojo +// shared buffer handle. +// +// Flags which may be set in |*flags| upon success: +// |MOJO_PLATFORM_SHARED_BUFFER_FLAG_READ_ONLY| is set iff the unwrapped +// shared buffer handle may only be mapped as read-only. +MOJO_SYSTEM_EXPORT MojoResult +MojoUnwrapPlatformSharedBufferHandle( + MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle, + size_t* num_bytes, + MojoPlatformSharedBufferHandleFlags* flags); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_PLATFORM_HANDLE_H_ diff --git a/mojo/public/c/system/set_thunks_for_app.cc b/mojo/public/c/system/set_thunks_for_app.cc new file mode 100644 index 0000000000000000000000000000000000000000..335cc02b79477bb45f28208414feb74d2f10471c --- /dev/null +++ b/mojo/public/c/system/set_thunks_for_app.cc @@ -0,0 +1,20 @@ +// Copyright 2016 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. + +#include "mojo/public/c/system/thunks.h" + +extern "C" { + +#if defined(WIN32) +#define THUNKS_EXPORT __declspec(dllexport) +#else +#define THUNKS_EXPORT __attribute__((visibility("default"))) +#endif + +THUNKS_EXPORT size_t MojoSetSystemThunks( + const MojoSystemThunks* system_thunks) { + return MojoEmbedderSetSystemThunks(system_thunks); +} + +} // extern "C" diff --git a/mojo/public/c/system/system_export.h b/mojo/public/c/system/system_export.h new file mode 100644 index 0000000000000000000000000000000000000000..775f6679f92a1b32b991ac803d3a972bf29838b8 --- /dev/null +++ b/mojo/public/c/system/system_export.h @@ -0,0 +1,33 @@ +// Copyright 2014 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 MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_ +#define MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_SYSTEM_IMPLEMENTATION) +#define MOJO_SYSTEM_EXPORT __declspec(dllexport) +#else +#define MOJO_SYSTEM_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_SYSTEM_IMPLEMENTATION) +#define MOJO_SYSTEM_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_SYSTEM_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) + +#define MOJO_SYSTEM_EXPORT + +#endif // defined(COMPONENT_BUILD) + +#endif // MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_ diff --git a/mojo/public/c/system/tests/BUILD.gn b/mojo/public/c/system/tests/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..bace63c4aa6d5ba77491c07fdda3ad7456656d40 --- /dev/null +++ b/mojo/public/c/system/tests/BUILD.gn @@ -0,0 +1,38 @@ +# Copyright 2014 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. + +source_set("tests") { + testonly = true + + visibility = [ + "//mojo/public/cpp/system/tests:mojo_public_system_unittests", + "//mojo/public/cpp/system/tests:tests", + ] + + sources = [ + "core_unittest.cc", + "core_unittest_pure_c.c", + "macros_unittest.cc", + ] + + deps = [ + "//mojo/public/c/system", + "//mojo/public/cpp/system", + "//testing/gtest", + ] +} + +source_set("perftests") { + testonly = true + + sources = [ + "core_perftest.cc", + ] + + deps = [ + "//mojo/public/cpp/system", + "//mojo/public/cpp/test_support:test_utils", + "//testing/gtest", + ] +} diff --git a/mojo/public/c/system/tests/core_perftest.cc b/mojo/public/c/system/tests/core_perftest.cc new file mode 100644 index 0000000000000000000000000000000000000000..cab465b43d75dfe9c4e16b3d483178ac3d74d4b0 --- /dev/null +++ b/mojo/public/c/system/tests/core_perftest.cc @@ -0,0 +1,329 @@ +// Copyright 2013 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. + +// This tests the performance of the C API. + +#include "mojo/public/c/system/core.h" + +#include +#include +#include + +#include "base/macros.h" +#include "base/threading/simple_thread.h" +#include "mojo/public/cpp/system/wait.h" +#include "mojo/public/cpp/test_support/test_support.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if !defined(WIN32) +#include +#endif // !defined(WIN32) + +namespace { + +#if !defined(WIN32) +class MessagePipeWriterThread : public base::SimpleThread { + public: + MessagePipeWriterThread(MojoHandle handle, uint32_t num_bytes) + : SimpleThread("MessagePipeWriterThread"), + handle_(handle), + num_bytes_(num_bytes), + num_writes_(0) {} + ~MessagePipeWriterThread() override {} + + void Run() override { + char buffer[10000]; + assert(num_bytes_ <= sizeof(buffer)); + + // TODO(vtl): Should I throttle somehow? + for (;;) { + MojoResult result = MojoWriteMessage(handle_, buffer, num_bytes_, nullptr, + 0, MOJO_WRITE_MESSAGE_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + num_writes_++; + continue; + } + + // We failed to write. + // Either |handle_| or its peer was closed. + assert(result == MOJO_RESULT_INVALID_ARGUMENT || + result == MOJO_RESULT_FAILED_PRECONDITION); + break; + } + } + + // Use only after joining the thread. + int64_t num_writes() const { return num_writes_; } + + private: + const MojoHandle handle_; + const uint32_t num_bytes_; + int64_t num_writes_; + + DISALLOW_COPY_AND_ASSIGN(MessagePipeWriterThread); +}; + +class MessagePipeReaderThread : public base::SimpleThread { + public: + explicit MessagePipeReaderThread(MojoHandle handle) + : SimpleThread("MessagePipeReaderThread"), + handle_(handle), + num_reads_(0) {} + ~MessagePipeReaderThread() override {} + + void Run() override { + char buffer[10000]; + + for (;;) { + uint32_t num_bytes = static_cast(sizeof(buffer)); + MojoResult result = MojoReadMessage(handle_, buffer, &num_bytes, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + num_reads_++; + continue; + } + + if (result == MOJO_RESULT_SHOULD_WAIT) { + result = mojo::Wait(mojo::Handle(handle_), MOJO_HANDLE_SIGNAL_READABLE); + if (result == MOJO_RESULT_OK) { + // Go to the top of the loop to read again. + continue; + } + } + + // We failed to read and possibly failed to wait. + // Either |handle_| or its peer was closed. + assert(result == MOJO_RESULT_INVALID_ARGUMENT || + result == MOJO_RESULT_FAILED_PRECONDITION); + break; + } + } + + // Use only after joining the thread. + int64_t num_reads() const { return num_reads_; } + + private: + const MojoHandle handle_; + int64_t num_reads_; + + DISALLOW_COPY_AND_ASSIGN(MessagePipeReaderThread); +}; +#endif // !defined(WIN32) + +class CorePerftest : public testing::Test { + public: + CorePerftest() : buffer_(nullptr), num_bytes_(0) {} + ~CorePerftest() override {} + + static void NoOp(void* /*closure*/) {} + + static void MessagePipe_CreateAndClose(void* closure) { + CorePerftest* self = static_cast(closure); + MojoResult result = MojoCreateMessagePipe(nullptr, &self->h0_, &self->h1_); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); + result = MojoClose(self->h0_); + assert(result == MOJO_RESULT_OK); + result = MojoClose(self->h1_); + assert(result == MOJO_RESULT_OK); + } + + static void MessagePipe_WriteAndRead(void* closure) { + CorePerftest* self = static_cast(closure); + MojoResult result = + MojoWriteMessage(self->h0_, self->buffer_, self->num_bytes_, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); + uint32_t read_bytes = self->num_bytes_; + result = MojoReadMessage(self->h1_, self->buffer_, &read_bytes, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE); + assert(result == MOJO_RESULT_OK); + } + + static void MessagePipe_EmptyRead(void* closure) { + CorePerftest* self = static_cast(closure); + MojoResult result = + MojoReadMessage(self->h0_, nullptr, nullptr, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_SHOULD_WAIT); + } + + protected: +#if !defined(WIN32) + void DoMessagePipeThreadedTest(unsigned num_writers, + unsigned num_readers, + uint32_t num_bytes) { + static const int64_t kPerftestTimeMicroseconds = 3 * 1000000; + + assert(num_writers > 0); + assert(num_readers > 0); + + MojoResult result = MojoCreateMessagePipe(nullptr, &h0_, &h1_); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); + + std::vector writers; + for (unsigned i = 0; i < num_writers; i++) + writers.push_back(new MessagePipeWriterThread(h0_, num_bytes)); + + std::vector readers; + for (unsigned i = 0; i < num_readers; i++) + readers.push_back(new MessagePipeReaderThread(h1_)); + + // Start time here, just before we fire off the threads. + const MojoTimeTicks start_time = MojoGetTimeTicksNow(); + + // Interleave the starts. + for (unsigned i = 0; i < num_writers || i < num_readers; i++) { + if (i < num_writers) + writers[i]->Start(); + if (i < num_readers) + readers[i]->Start(); + } + + Sleep(kPerftestTimeMicroseconds); + + // Close both handles to make writers and readers stop immediately. + result = MojoClose(h0_); + assert(result == MOJO_RESULT_OK); + result = MojoClose(h1_); + assert(result == MOJO_RESULT_OK); + + // Join everything. + for (unsigned i = 0; i < num_writers; i++) + writers[i]->Join(); + for (unsigned i = 0; i < num_readers; i++) + readers[i]->Join(); + + // Stop time here. + MojoTimeTicks end_time = MojoGetTimeTicksNow(); + + // Add up write and read counts, and destroy the threads. + int64_t num_writes = 0; + for (unsigned i = 0; i < num_writers; i++) { + num_writes += writers[i]->num_writes(); + delete writers[i]; + } + writers.clear(); + int64_t num_reads = 0; + for (unsigned i = 0; i < num_readers; i++) { + num_reads += readers[i]->num_reads(); + delete readers[i]; + } + readers.clear(); + + char sub_test_name[200]; + sprintf(sub_test_name, "%uw_%ur_%ubytes", num_writers, num_readers, + static_cast(num_bytes)); + mojo::test::LogPerfResult( + "MessagePipe_Threaded_Writes", sub_test_name, + 1000000.0 * static_cast(num_writes) / (end_time - start_time), + "writes/second"); + mojo::test::LogPerfResult( + "MessagePipe_Threaded_Reads", sub_test_name, + 1000000.0 * static_cast(num_reads) / (end_time - start_time), + "reads/second"); + } +#endif // !defined(WIN32) + + MojoHandle h0_; + MojoHandle h1_; + + void* buffer_; + uint32_t num_bytes_; + + private: +#if !defined(WIN32) + void Sleep(int64_t microseconds) { + struct timespec req = { + static_cast(microseconds / 1000000), // Seconds. + static_cast(microseconds % 1000000) * 1000L // Nanoseconds. + }; + int rv = nanosleep(&req, nullptr); + ALLOW_UNUSED_LOCAL(rv); + assert(rv == 0); + } +#endif // !defined(WIN32) + + DISALLOW_COPY_AND_ASSIGN(CorePerftest); +}; + +// A no-op test so we can compare performance. +TEST_F(CorePerftest, NoOp) { + mojo::test::IterateAndReportPerf("Iterate_NoOp", nullptr, &CorePerftest::NoOp, + this); +} + +TEST_F(CorePerftest, MessagePipe_CreateAndClose) { + mojo::test::IterateAndReportPerf("MessagePipe_CreateAndClose", nullptr, + &CorePerftest::MessagePipe_CreateAndClose, + this); +} + +TEST_F(CorePerftest, MessagePipe_WriteAndRead) { + MojoResult result = MojoCreateMessagePipe(nullptr, &h0_, &h1_); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); + char buffer[10000] = {0}; + buffer_ = buffer; + num_bytes_ = 10u; + mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "10bytes", + &CorePerftest::MessagePipe_WriteAndRead, + this); + num_bytes_ = 100u; + mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "100bytes", + &CorePerftest::MessagePipe_WriteAndRead, + this); + num_bytes_ = 1000u; + mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "1000bytes", + &CorePerftest::MessagePipe_WriteAndRead, + this); + num_bytes_ = 10000u; + mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "10000bytes", + &CorePerftest::MessagePipe_WriteAndRead, + this); + result = MojoClose(h0_); + assert(result == MOJO_RESULT_OK); + result = MojoClose(h1_); + assert(result == MOJO_RESULT_OK); +} + +TEST_F(CorePerftest, MessagePipe_EmptyRead) { + MojoResult result = MojoCreateMessagePipe(nullptr, &h0_, &h1_); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); + mojo::test::IterateAndReportPerf("MessagePipe_EmptyRead", nullptr, + &CorePerftest::MessagePipe_EmptyRead, this); + result = MojoClose(h0_); + assert(result == MOJO_RESULT_OK); + result = MojoClose(h1_); + assert(result == MOJO_RESULT_OK); +} + +#if !defined(WIN32) +TEST_F(CorePerftest, MessagePipe_Threaded) { + DoMessagePipeThreadedTest(1u, 1u, 100u); + DoMessagePipeThreadedTest(2u, 2u, 100u); + DoMessagePipeThreadedTest(3u, 3u, 100u); + DoMessagePipeThreadedTest(10u, 10u, 100u); + DoMessagePipeThreadedTest(10u, 1u, 100u); + DoMessagePipeThreadedTest(1u, 10u, 100u); + + // For comparison of overhead: + DoMessagePipeThreadedTest(1u, 1u, 10u); + // 100 was done above. + DoMessagePipeThreadedTest(1u, 1u, 1000u); + DoMessagePipeThreadedTest(1u, 1u, 10000u); + + DoMessagePipeThreadedTest(3u, 3u, 10u); + // 100 was done above. + DoMessagePipeThreadedTest(3u, 3u, 1000u); + DoMessagePipeThreadedTest(3u, 3u, 10000u); +} +#endif // !defined(WIN32) + +} // namespace diff --git a/mojo/public/c/system/tests/core_unittest.cc b/mojo/public/c/system/tests/core_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..a9da2557170cf989cdcd83c47c81e8b2c3268eb2 --- /dev/null +++ b/mojo/public/c/system/tests/core_unittest.cc @@ -0,0 +1,322 @@ +// Copyright 2013 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. + +// This file tests the C API. + +#include "mojo/public/c/system/core.h" + +#include +#include + +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +const MojoHandleSignals kSignalReadadableWritable = + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE; + +const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED; + +TEST(CoreTest, GetTimeTicksNow) { + const MojoTimeTicks start = MojoGetTimeTicksNow(); + EXPECT_NE(static_cast(0), start) + << "MojoGetTimeTicksNow should return nonzero value"; +} + +// The only handle that's guaranteed to be invalid is |MOJO_HANDLE_INVALID|. +// Tests that everything that takes a handle properly recognizes it. +TEST(CoreTest, InvalidHandle) { + MojoHandle h0, h1; + char buffer[10] = {0}; + uint32_t buffer_size; + void* write_pointer; + const void* read_pointer; + + // Close: + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(MOJO_HANDLE_INVALID)); + + // Message pipe: + h0 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWriteMessage(h0, buffer, 3, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + buffer_size = static_cast(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoReadMessage(h0, buffer, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Data pipe: + buffer_size = static_cast(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWriteData(h0, buffer, &buffer_size, MOJO_WRITE_DATA_FLAG_NONE)); + write_pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoBeginWriteData(h0, &write_pointer, &buffer_size, + MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoEndWriteData(h0, 1)); + buffer_size = static_cast(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoReadData(h0, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE)); + read_pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoBeginReadData(h0, &read_pointer, &buffer_size, + MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoEndReadData(h0, 1)); + + // Shared buffer: + h1 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoDuplicateBufferHandle(h0, nullptr, &h1)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoMapBuffer(h0, 0, 1, &write_pointer, MOJO_MAP_BUFFER_FLAG_NONE)); +} + +TEST(CoreTest, BasicMessagePipe) { + MojoHandle h0, h1; + MojoHandleSignals sig; + char buffer[10] = {0}; + uint32_t buffer_size; + + h0 = MOJO_HANDLE_INVALID; + h1 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &h0, &h1)); + EXPECT_NE(h0, MOJO_HANDLE_INVALID); + EXPECT_NE(h1, MOJO_HANDLE_INVALID); + + // Shouldn't be readable, we haven't written anything. Should be writable. + MojoHandleSignalsState state; + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(h0, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + // Try to read. + buffer_size = static_cast(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + MojoReadMessage(h0, buffer, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Write to |h1|. + static const char kHello[] = "hello"; + buffer_size = static_cast(sizeof(kHello)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWriteMessage(h1, kHello, buffer_size, nullptr, + 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // |h0| should be readable. + size_t result_index = 1; + MojoHandleSignalsState states[1]; + sig = MOJO_HANDLE_SIGNAL_READABLE; + Handle handle0(h0); + EXPECT_EQ(MOJO_RESULT_OK, + mojo::WaitMany(&handle0, &sig, 1, &result_index, states)); + + EXPECT_EQ(0u, result_index); + EXPECT_EQ(kSignalReadadableWritable, states[0].satisfied_signals); + EXPECT_EQ(kSignalAll, states[0].satisfiable_signals); + + // Read from |h0|. + buffer_size = static_cast(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(h0, buffer, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(static_cast(sizeof(kHello)), buffer_size); + EXPECT_STREQ(kHello, buffer); + + // |h0| should no longer be readable. + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(h0, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + // Close |h0|. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0)); + + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h1), + MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); + + // |h1| should no longer be readable or writable. + EXPECT_EQ( + MOJO_RESULT_FAILED_PRECONDITION, + mojo::Wait(mojo::Handle(h1), + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + &state)); + + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1)); +} + +TEST(CoreTest, BasicDataPipe) { + MojoHandle hp, hc; + MojoHandleSignals sig; + char buffer[20] = {0}; + uint32_t buffer_size; + void* write_pointer; + const void* read_pointer; + + hp = MOJO_HANDLE_INVALID; + hc = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(nullptr, &hp, &hc)); + EXPECT_NE(hp, MOJO_HANDLE_INVALID); + EXPECT_NE(hc, MOJO_HANDLE_INVALID); + + // The consumer |hc| shouldn't be readable. + MojoHandleSignalsState state; + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(hc, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_NONE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfiable_signals); + + // The producer |hp| should be writable. + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(hp, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + // Try to read from |hc|. + buffer_size = static_cast(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + MojoReadData(hc, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE)); + + // Try to begin a two-phase read from |hc|. + read_pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + MojoBeginReadData(hc, &read_pointer, &buffer_size, + MOJO_READ_DATA_FLAG_NONE)); + + // Write to |hp|. + static const char kHello[] = "hello "; + // Don't include terminating null. + buffer_size = static_cast(strlen(kHello)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWriteData(hp, kHello, &buffer_size, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // |hc| should be(come) readable. + size_t result_index = 1; + MojoHandleSignalsState states[1]; + sig = MOJO_HANDLE_SIGNAL_READABLE; + Handle consumer_handle(hc); + EXPECT_EQ(MOJO_RESULT_OK, + mojo::WaitMany(&consumer_handle, &sig, 1, &result_index, states)); + + EXPECT_EQ(0u, result_index); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + states[0].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + states[0].satisfiable_signals); + + // Do a two-phase write to |hp|. + EXPECT_EQ(MOJO_RESULT_OK, MojoBeginWriteData(hp, &write_pointer, &buffer_size, + MOJO_WRITE_DATA_FLAG_NONE)); + static const char kWorld[] = "world"; + ASSERT_GE(buffer_size, sizeof(kWorld)); + // Include the terminating null. + memcpy(write_pointer, kWorld, sizeof(kWorld)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoEndWriteData(hp, static_cast(sizeof(kWorld)))); + + // Read one character from |hc|. + memset(buffer, 0, sizeof(buffer)); + buffer_size = 1; + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadData(hc, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE)); + + // Close |hp|. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(hp)); + + // |hc| should still be readable. + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(hc), + MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); + + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + // Do a two-phase read from |hc|. + read_pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_OK, MojoBeginReadData(hc, &read_pointer, &buffer_size, + MOJO_READ_DATA_FLAG_NONE)); + ASSERT_LE(buffer_size, sizeof(buffer) - 1); + memcpy(&buffer[1], read_pointer, buffer_size); + EXPECT_EQ(MOJO_RESULT_OK, MojoEndReadData(hc, buffer_size)); + EXPECT_STREQ("hello world", buffer); + + // |hc| should no longer be readable. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + mojo::Wait(mojo::Handle(hc), MOJO_HANDLE_SIGNAL_READABLE, &state)); + + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(hc)); + + // TODO(vtl): Test the other way around -- closing the consumer should make + // the producer never-writable? +} + +TEST(CoreTest, BasicSharedBuffer) { + MojoHandle h0, h1; + void* pointer; + + // Create a shared buffer (|h0|). + h0 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateSharedBuffer(nullptr, 100, &h0)); + EXPECT_NE(h0, MOJO_HANDLE_INVALID); + + // Map everything. + pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(h0, 0, 100, &pointer, MOJO_MAP_BUFFER_FLAG_NONE)); + ASSERT_TRUE(pointer); + static_cast(pointer)[50] = 'x'; + + // Duplicate |h0| to |h1|. + h1 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoDuplicateBufferHandle(h0, nullptr, &h1)); + EXPECT_NE(h1, MOJO_HANDLE_INVALID); + + // Close |h0|. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0)); + + // The mapping should still be good. + static_cast(pointer)[51] = 'y'; + + // Unmap it. + EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(pointer)); + + // Map half of |h1|. + pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(h1, 50, 50, &pointer, MOJO_MAP_BUFFER_FLAG_NONE)); + ASSERT_TRUE(pointer); + + // It should have what we wrote. + EXPECT_EQ('x', static_cast(pointer)[0]); + EXPECT_EQ('y', static_cast(pointer)[1]); + + // Unmap it. + EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(pointer)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1)); +} + +// Defined in core_unittest_pure_c.c. +extern "C" const char* MinimalCTest(void); + +// This checks that things actually work in C (not C++). +TEST(CoreTest, MinimalCTest) { + const char* failure = MinimalCTest(); + EXPECT_FALSE(failure) << failure; +} + +// TODO(vtl): Add multi-threaded tests. + +} // namespace +} // namespace mojo diff --git a/mojo/public/c/system/tests/core_unittest_pure_c.c b/mojo/public/c/system/tests/core_unittest_pure_c.c new file mode 100644 index 0000000000000000000000000000000000000000..3164649cbd71df02fe8366444f51a88de7b703d4 --- /dev/null +++ b/mojo/public/c/system/tests/core_unittest_pure_c.c @@ -0,0 +1,77 @@ +// Copyright 2014 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. + +#ifdef __cplusplus +#error "This file should be compiled as C, not C++." +#endif + +#include +#include +#include + +// Include all the header files that are meant to be compilable as C. Start with +// core.h, since it's the most important one. +#include "mojo/public/c/system/core.h" +#include "mojo/public/c/system/macros.h" + +// The joys of the C preprocessor.... +#define STRINGIFY(x) #x +#define STRINGIFY2(x) STRINGIFY(x) +#define FAILURE(message) \ + __FILE__ "(" STRINGIFY2(__LINE__) "): Failure: " message + +// Makeshift gtest. +#define EXPECT_EQ(a, b) \ + do { \ + if ((a) != (b)) \ + return FAILURE(STRINGIFY(a) " != " STRINGIFY(b) " (expected ==)"); \ + } while (0) +#define EXPECT_NE(a, b) \ + do { \ + if ((a) == (b)) \ + return FAILURE(STRINGIFY(a) " == " STRINGIFY(b) " (expected !=)"); \ + } while (0) + +// This function exists mainly to be compiled and linked. We do some cursory +// checks and call it from a unit test, to make sure that link problems aren't +// missed due to deadstripping. Returns null on success and a string on failure +// (describing the failure). +const char* MinimalCTest(void) { + // MSVS before 2013 *really* only supports C90: All variables must be declared + // at the top. (MSVS 2013 is more reasonable.) + MojoTimeTicks ticks; + MojoHandle handle0, handle1; + const char kHello[] = "hello"; + char buffer[200] = {0}; + uint32_t num_bytes; + + ticks = MojoGetTimeTicksNow(); + EXPECT_NE(ticks, 0); + + handle0 = MOJO_HANDLE_INVALID; + EXPECT_NE(MOJO_RESULT_OK, MojoClose(handle0)); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoQueryHandleSignalsState(handle0, NULL)); + + handle1 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(NULL, &handle0, &handle1)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(handle0, kHello, (uint32_t)sizeof(kHello), NULL, + 0u, MOJO_WRITE_DATA_FLAG_NONE)); + + num_bytes = (uint32_t)sizeof(buffer); + EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(handle1, buffer, &num_bytes, NULL, + NULL, MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ((uint32_t)sizeof(kHello), num_bytes); + EXPECT_EQ(0, memcmp(buffer, kHello, sizeof(kHello))); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handle0)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handle1)); + + // TODO(vtl): data pipe + + return NULL; +} diff --git a/mojo/public/c/system/tests/macros_unittest.cc b/mojo/public/c/system/tests/macros_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..fb9ff764057adc3e5d9c782e15a6f7c426e87bfe --- /dev/null +++ b/mojo/public/c/system/tests/macros_unittest.cc @@ -0,0 +1,68 @@ +// Copyright 2014 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. + +// This file tests the C Mojo system macros and consists of "positive" tests, +// i.e., those verifying that things work (without compile errors, or even +// warnings if warnings are treated as errors). +// TODO(vtl): Fix no-compile tests (which are all disabled; crbug.com/105388) +// and write some "negative" tests. + +#include "mojo/public/c/system/macros.h" + +#include +#include +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +// First test |MOJO_STATIC_ASSERT()| in a global scope. +MOJO_STATIC_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t), + "Bad static_assert() failure in global scope"); + +TEST(MacrosTest, CompileAssert) { + // Then in a local scope. + MOJO_STATIC_ASSERT(sizeof(int32_t) == 2 * sizeof(int16_t), + "Bad static_assert() failure"); +} + +TEST(MacrosTest, Alignof) { + // Strictly speaking, this isn't a portable test, but I think it'll pass on + // all the platforms we currently support. + EXPECT_EQ(1u, MOJO_ALIGNOF(char)); + EXPECT_EQ(4u, MOJO_ALIGNOF(int32_t)); + EXPECT_EQ(8u, MOJO_ALIGNOF(int64_t)); + EXPECT_EQ(8u, MOJO_ALIGNOF(double)); +} + +// These structs are used in the Alignas test. Define them globally to avoid +// MSVS warnings/errors. +#if defined(_MSC_VER) +#pragma warning(push) +// Disable the warning "structure was padded due to __declspec(align())". +#pragma warning(disable : 4324) +#endif +struct MOJO_ALIGNAS(1) StructAlignas1 { + char x; +}; +struct MOJO_ALIGNAS(4) StructAlignas4 { + char x; +}; +struct MOJO_ALIGNAS(8) StructAlignas8 { + char x; +}; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +TEST(MacrosTest, Alignas) { + EXPECT_EQ(1u, MOJO_ALIGNOF(StructAlignas1)); + EXPECT_EQ(4u, MOJO_ALIGNOF(StructAlignas4)); + EXPECT_EQ(8u, MOJO_ALIGNOF(StructAlignas8)); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/c/system/thunks.cc b/mojo/public/c/system/thunks.cc new file mode 100644 index 0000000000000000000000000000000000000000..67c568f7d79a8cf47674fc5930a612995a599d78 --- /dev/null +++ b/mojo/public/c/system/thunks.cc @@ -0,0 +1,273 @@ +// Copyright 2013 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. + +#include "mojo/public/c/system/thunks.h" + +#include +#include +#include + +extern "C" { + +static MojoSystemThunks g_thunks = {0}; + +MojoTimeTicks MojoGetTimeTicksNow() { + assert(g_thunks.GetTimeTicksNow); + return g_thunks.GetTimeTicksNow(); +} + +MojoResult MojoClose(MojoHandle handle) { + assert(g_thunks.Close); + return g_thunks.Close(handle); +} + +MojoResult MojoQueryHandleSignalsState( + MojoHandle handle, + struct MojoHandleSignalsState* signals_state) { + assert(g_thunks.QueryHandleSignalsState); + return g_thunks.QueryHandleSignalsState(handle, signals_state); +} + +MojoResult MojoCreateMessagePipe(const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1) { + assert(g_thunks.CreateMessagePipe); + return g_thunks.CreateMessagePipe(options, message_pipe_handle0, + message_pipe_handle1); +} + +MojoResult MojoWriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + assert(g_thunks.WriteMessage); + return g_thunks.WriteMessage(message_pipe_handle, bytes, num_bytes, handles, + num_handles, flags); +} + +MojoResult MojoReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + assert(g_thunks.ReadMessage); + return g_thunks.ReadMessage(message_pipe_handle, bytes, num_bytes, handles, + num_handles, flags); +} + +MojoResult MojoCreateDataPipe(const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle) { + assert(g_thunks.CreateDataPipe); + return g_thunks.CreateDataPipe(options, data_pipe_producer_handle, + data_pipe_consumer_handle); +} + +MojoResult MojoWriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_elements, + MojoWriteDataFlags flags) { + assert(g_thunks.WriteData); + return g_thunks.WriteData(data_pipe_producer_handle, elements, num_elements, + flags); +} + +MojoResult MojoBeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_elements, + MojoWriteDataFlags flags) { + assert(g_thunks.BeginWriteData); + return g_thunks.BeginWriteData(data_pipe_producer_handle, buffer, + buffer_num_elements, flags); +} + +MojoResult MojoEndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_elements_written) { + assert(g_thunks.EndWriteData); + return g_thunks.EndWriteData(data_pipe_producer_handle, num_elements_written); +} + +MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_elements, + MojoReadDataFlags flags) { + assert(g_thunks.ReadData); + return g_thunks.ReadData(data_pipe_consumer_handle, elements, num_elements, + flags); +} + +MojoResult MojoBeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_elements, + MojoReadDataFlags flags) { + assert(g_thunks.BeginReadData); + return g_thunks.BeginReadData(data_pipe_consumer_handle, buffer, + buffer_num_elements, flags); +} + +MojoResult MojoEndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_elements_read) { + assert(g_thunks.EndReadData); + return g_thunks.EndReadData(data_pipe_consumer_handle, num_elements_read); +} + +MojoResult MojoCreateSharedBuffer( + const struct MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle) { + assert(g_thunks.CreateSharedBuffer); + return g_thunks.CreateSharedBuffer(options, num_bytes, shared_buffer_handle); +} + +MojoResult MojoDuplicateBufferHandle( + MojoHandle buffer_handle, + const struct MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle) { + assert(g_thunks.DuplicateBufferHandle); + return g_thunks.DuplicateBufferHandle(buffer_handle, options, + new_buffer_handle); +} + +MojoResult MojoMapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags) { + assert(g_thunks.MapBuffer); + return g_thunks.MapBuffer(buffer_handle, offset, num_bytes, buffer, flags); +} + +MojoResult MojoUnmapBuffer(void* buffer) { + assert(g_thunks.UnmapBuffer); + return g_thunks.UnmapBuffer(buffer); +} + +MojoResult MojoCreateWatcher(MojoWatcherCallback callback, + MojoHandle* watcher_handle) { + assert(g_thunks.CreateWatcher); + return g_thunks.CreateWatcher(callback, watcher_handle); +} + +MojoResult MojoWatch(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context) { + assert(g_thunks.Watch); + return g_thunks.Watch(watcher_handle, handle, signals, context); +} + +MojoResult MojoCancelWatch(MojoHandle watcher_handle, uintptr_t context) { + assert(g_thunks.CancelWatch); + return g_thunks.CancelWatch(watcher_handle, context); +} + +MojoResult MojoArmWatcher(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + assert(g_thunks.ArmWatcher); + return g_thunks.ArmWatcher(watcher_handle, num_ready_contexts, ready_contexts, + ready_results, ready_signals_states); +} + +MojoResult MojoFuseMessagePipes(MojoHandle handle0, MojoHandle handle1) { + assert(g_thunks.FuseMessagePipes); + return g_thunks.FuseMessagePipes(handle0, handle1); +} + +MojoResult MojoWriteMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags flags) { + assert(g_thunks.WriteMessageNew); + return g_thunks.WriteMessageNew(message_pipe_handle, message, flags); +} + +MojoResult MojoReadMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + assert(g_thunks.ReadMessageNew); + return g_thunks.ReadMessageNew(message_pipe_handle, message, num_bytes, + handles, num_handles, flags); +} + +MojoResult MojoAllocMessage(uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoAllocMessageFlags flags, + MojoMessageHandle* message) { + assert(g_thunks.AllocMessage); + return g_thunks.AllocMessage( + num_bytes, handles, num_handles, flags, message); +} + +MojoResult MojoFreeMessage(MojoMessageHandle message) { + assert(g_thunks.FreeMessage); + return g_thunks.FreeMessage(message); +} + +MojoResult MojoGetMessageBuffer(MojoMessageHandle message, void** buffer) { + assert(g_thunks.GetMessageBuffer); + return g_thunks.GetMessageBuffer(message, buffer); +} + +MojoResult MojoWrapPlatformHandle( + const struct MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle) { + assert(g_thunks.WrapPlatformHandle); + return g_thunks.WrapPlatformHandle(platform_handle, mojo_handle); +} + +MojoResult MojoUnwrapPlatformHandle( + MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle) { + assert(g_thunks.UnwrapPlatformHandle); + return g_thunks.UnwrapPlatformHandle(mojo_handle, platform_handle); +} + +MojoResult MojoWrapPlatformSharedBufferHandle( + const struct MojoPlatformHandle* platform_handle, + size_t num_bytes, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle) { + assert(g_thunks.WrapPlatformSharedBufferHandle); + return g_thunks.WrapPlatformSharedBufferHandle(platform_handle, num_bytes, + flags, mojo_handle); +} + +MojoResult MojoUnwrapPlatformSharedBufferHandle( + MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle, + size_t* num_bytes, + MojoPlatformSharedBufferHandleFlags* flags) { + assert(g_thunks.UnwrapPlatformSharedBufferHandle); + return g_thunks.UnwrapPlatformSharedBufferHandle(mojo_handle, platform_handle, + num_bytes, flags); +} + +MojoResult MojoNotifyBadMessage(MojoMessageHandle message, + const char* error, + size_t error_num_bytes) { + assert(g_thunks.NotifyBadMessage); + return g_thunks.NotifyBadMessage(message, error, error_num_bytes); +} + +MojoResult MojoGetProperty(MojoPropertyType type, void* value) { + assert(g_thunks.GetProperty); + return g_thunks.GetProperty(type, value); +} + +} // extern "C" + +size_t MojoEmbedderSetSystemThunks(const MojoSystemThunks* system_thunks) { + if (system_thunks->size >= sizeof(g_thunks)) + g_thunks = *system_thunks; + return sizeof(g_thunks); +} diff --git a/mojo/public/c/system/thunks.h b/mojo/public/c/system/thunks.h new file mode 100644 index 0000000000000000000000000000000000000000..e61bb46a46e0fdfa1686976b24554de7b6b31923 --- /dev/null +++ b/mojo/public/c/system/thunks.h @@ -0,0 +1,148 @@ +// Copyright 2014 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. + +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_THUNKS_H_ +#define MOJO_PUBLIC_C_SYSTEM_THUNKS_H_ + +#include +#include + +#include "mojo/public/c/system/core.h" +#include "mojo/public/c/system/system_export.h" + +// Structure used to bind the basic Mojo Core functions to an embedder +// implementation. This is intended to eventually be used as a stable ABI +// between a Mojo embedder and some loaded application code, but for now it is +// still effectively safe to rearrange entries as needed. +#pragma pack(push, 8) +struct MojoSystemThunks { + size_t size; // Should be set to sizeof(MojoSystemThunks). + MojoTimeTicks (*GetTimeTicksNow)(); + MojoResult (*Close)(MojoHandle handle); + MojoResult (*QueryHandleSignalsState)( + MojoHandle handle, + struct MojoHandleSignalsState* signals_state); + MojoResult (*CreateMessagePipe)( + const struct MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1); + MojoResult (*WriteMessage)(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags); + MojoResult (*ReadMessage)(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags); + MojoResult (*CreateDataPipe)(const struct MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle); + MojoResult (*WriteData)(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_elements, + MojoWriteDataFlags flags); + MojoResult (*BeginWriteData)(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_elements, + MojoWriteDataFlags flags); + MojoResult (*EndWriteData)(MojoHandle data_pipe_producer_handle, + uint32_t num_elements_written); + MojoResult (*ReadData)(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_elements, + MojoReadDataFlags flags); + MojoResult (*BeginReadData)(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_elements, + MojoReadDataFlags flags); + MojoResult (*EndReadData)(MojoHandle data_pipe_consumer_handle, + uint32_t num_elements_read); + MojoResult (*CreateSharedBuffer)( + const struct MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle); + MojoResult (*DuplicateBufferHandle)( + MojoHandle buffer_handle, + const struct MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle); + MojoResult (*MapBuffer)(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags); + MojoResult (*UnmapBuffer)(void* buffer); + MojoResult (*CreateWatcher)(MojoWatcherCallback callback, + MojoHandle* watcher_handle); + MojoResult (*Watch)(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context); + MojoResult (*CancelWatch)(MojoHandle watcher_handle, uintptr_t context); + MojoResult (*ArmWatcher)(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states); + MojoResult (*FuseMessagePipes)(MojoHandle handle0, MojoHandle handle1); + MojoResult (*WriteMessageNew)(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags flags); + MojoResult (*ReadMessageNew)(MojoHandle message_pipe_handle, + MojoMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags); + MojoResult (*AllocMessage)(uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoAllocMessageFlags flags, + MojoMessageHandle* message); + MojoResult (*FreeMessage)(MojoMessageHandle message); + MojoResult (*GetMessageBuffer)(MojoMessageHandle message, void** buffer); + MojoResult (*WrapPlatformHandle)( + const struct MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle); + MojoResult (*UnwrapPlatformHandle)( + MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle); + MojoResult (*WrapPlatformSharedBufferHandle)( + const struct MojoPlatformHandle* platform_handle, + size_t num_bytes, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle); + MojoResult (*UnwrapPlatformSharedBufferHandle)( + MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle, + size_t* num_bytes, + MojoPlatformSharedBufferHandleFlags* flags); + MojoResult (*NotifyBadMessage)(MojoMessageHandle message, + const char* error, + size_t error_num_bytes); + MojoResult (*GetProperty)(MojoPropertyType type, void* value); +}; +#pragma pack(pop) + +// Use this type for the function found by dynamically discovering it in +// a DSO linked with mojo_system. For example: +// MojoSetSystemThunksFn mojo_set_system_thunks_fn = +// reinterpret_cast(app_library.GetFunctionPointer( +// "MojoSetSystemThunks")); +// The expected size of |system_thunks| is returned. +// The contents of |system_thunks| are copied. +typedef size_t (*MojoSetSystemThunksFn)( + const struct MojoSystemThunks* system_thunks); + +// A function for setting up the embedder's own system thunks. This should only +// be called by Mojo embedder code. +MOJO_SYSTEM_EXPORT size_t MojoEmbedderSetSystemThunks( + const struct MojoSystemThunks* system_thunks); + +#endif // MOJO_PUBLIC_C_SYSTEM_THUNKS_H_ diff --git a/mojo/public/c/system/types.h b/mojo/public/c/system/types.h new file mode 100644 index 0000000000000000000000000000000000000000..15813b6b734ddb7e7c0071dcbc783d071abfe893 --- /dev/null +++ b/mojo/public/c/system/types.h @@ -0,0 +1,223 @@ +// Copyright 2014 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. + +// This file contains types and constants/macros common to different Mojo system +// APIs. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_TYPES_H_ +#define MOJO_PUBLIC_C_SYSTEM_TYPES_H_ + +#include + +#include "mojo/public/c/system/macros.h" + +// |MojoTimeTicks|: A time delta, in microseconds, the meaning of which is +// source-dependent. + +typedef int64_t MojoTimeTicks; + +// |MojoHandle|: Handles to Mojo objects. +// |MOJO_HANDLE_INVALID| - A value that is never a valid handle. + +typedef uint32_t MojoHandle; + +#ifdef __cplusplus +const MojoHandle MOJO_HANDLE_INVALID = 0; +#else +#define MOJO_HANDLE_INVALID ((MojoHandle)0) +#endif + +// |MojoResult|: Result codes for Mojo operations. The only success code is zero +// (|MOJO_RESULT_OK|); all non-zero values should be considered as error/failure +// codes (even if the value is not recognized). +// |MOJO_RESULT_OK| - Not an error; returned on success. +// |MOJO_RESULT_CANCELLED| - Operation was cancelled, typically by the caller. +// |MOJO_RESULT_UNKNOWN| - Unknown error (e.g., if not enough information is +// available for a more specific error). +// |MOJO_RESULT_INVALID_ARGUMENT| - Caller specified an invalid argument. This +// differs from |MOJO_RESULT_FAILED_PRECONDITION| in that the former +// indicates arguments that are invalid regardless of the state of the +// system. +// |MOJO_RESULT_DEADLINE_EXCEEDED| - Deadline expired before the operation +// could complete. +// |MOJO_RESULT_NOT_FOUND| - Some requested entity was not found (i.e., does +// not exist). +// |MOJO_RESULT_ALREADY_EXISTS| - Some entity or condition that we attempted +// to create already exists. +// |MOJO_RESULT_PERMISSION_DENIED| - The caller does not have permission to +// for the operation (use |MOJO_RESULT_RESOURCE_EXHAUSTED| for rejections +// caused by exhausting some resource instead). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| - Some resource required for the call +// (possibly some quota) has been exhausted. +// |MOJO_RESULT_FAILED_PRECONDITION| - The system is not in a state required +// for the operation (use this if the caller must do something to rectify +// the state before retrying). +// |MOJO_RESULT_ABORTED| - The operation was aborted by the system, possibly +// due to a concurrency issue (use this if the caller may retry at a +// higher level). +// |MOJO_RESULT_OUT_OF_RANGE| - The operation was attempted past the valid +// range. Unlike |MOJO_RESULT_INVALID_ARGUMENT|, this indicates that the +// operation may be/become valid depending on the system state. (This +// error is similar to |MOJO_RESULT_FAILED_PRECONDITION|, but is more +// specific.) +// |MOJO_RESULT_UNIMPLEMENTED| - The operation is not implemented, supported, +// or enabled. +// |MOJO_RESULT_INTERNAL| - Internal error: this should never happen and +// indicates that some invariant expected by the system has been broken. +// |MOJO_RESULT_UNAVAILABLE| - The operation is (temporarily) currently +// unavailable. The caller may simply retry the operation (possibly with a +// backoff). +// |MOJO_RESULT_DATA_LOSS| - Unrecoverable data loss or corruption. +// |MOJO_RESULT_BUSY| - One of the resources involved is currently being used +// (possibly on another thread) in a way that prevents the current +// operation from proceeding, e.g., if the other operation may result in +// the resource being invalidated. +// |MOJO_RESULT_SHOULD_WAIT| - The request cannot currently be completed +// (e.g., if the data requested is not yet available). The caller should +// wait for it to be feasible using a watcher. +// +// The codes from |MOJO_RESULT_OK| to |MOJO_RESULT_DATA_LOSS| come from +// Google3's canonical error codes. + +typedef uint32_t MojoResult; + +#ifdef __cplusplus +const MojoResult MOJO_RESULT_OK = 0; +const MojoResult MOJO_RESULT_CANCELLED = 1; +const MojoResult MOJO_RESULT_UNKNOWN = 2; +const MojoResult MOJO_RESULT_INVALID_ARGUMENT = 3; +const MojoResult MOJO_RESULT_DEADLINE_EXCEEDED = 4; +const MojoResult MOJO_RESULT_NOT_FOUND = 5; +const MojoResult MOJO_RESULT_ALREADY_EXISTS = 6; +const MojoResult MOJO_RESULT_PERMISSION_DENIED = 7; +const MojoResult MOJO_RESULT_RESOURCE_EXHAUSTED = 8; +const MojoResult MOJO_RESULT_FAILED_PRECONDITION = 9; +const MojoResult MOJO_RESULT_ABORTED = 10; +const MojoResult MOJO_RESULT_OUT_OF_RANGE = 11; +const MojoResult MOJO_RESULT_UNIMPLEMENTED = 12; +const MojoResult MOJO_RESULT_INTERNAL = 13; +const MojoResult MOJO_RESULT_UNAVAILABLE = 14; +const MojoResult MOJO_RESULT_DATA_LOSS = 15; +const MojoResult MOJO_RESULT_BUSY = 16; +const MojoResult MOJO_RESULT_SHOULD_WAIT = 17; +#else +#define MOJO_RESULT_OK ((MojoResult)0) +#define MOJO_RESULT_CANCELLED ((MojoResult)1) +#define MOJO_RESULT_UNKNOWN ((MojoResult)2) +#define MOJO_RESULT_INVALID_ARGUMENT ((MojoResult)3) +#define MOJO_RESULT_DEADLINE_EXCEEDED ((MojoResult)4) +#define MOJO_RESULT_NOT_FOUND ((MojoResult)5) +#define MOJO_RESULT_ALREADY_EXISTS ((MojoResult)6) +#define MOJO_RESULT_PERMISSION_DENIED ((MojoResult)7) +#define MOJO_RESULT_RESOURCE_EXHAUSTED ((MojoResult)8) +#define MOJO_RESULT_FAILED_PRECONDITION ((MojoResult)9) +#define MOJO_RESULT_ABORTED ((MojoResult)10) +#define MOJO_RESULT_OUT_OF_RANGE ((MojoResult)11) +#define MOJO_RESULT_UNIMPLEMENTED ((MojoResult)12) +#define MOJO_RESULT_INTERNAL ((MojoResult)13) +#define MOJO_RESULT_UNAVAILABLE ((MojoResult)14) +#define MOJO_RESULT_DATA_LOSS ((MojoResult)15) +#define MOJO_RESULT_BUSY ((MojoResult)16) +#define MOJO_RESULT_SHOULD_WAIT ((MojoResult)17) +#endif + +// |MojoDeadline|: Used to specify deadlines (timeouts), in microseconds (except +// for |MOJO_DEADLINE_INDEFINITE|). +// |MOJO_DEADLINE_INDEFINITE| - Used to indicate "forever". + +typedef uint64_t MojoDeadline; + +#ifdef __cplusplus +const MojoDeadline MOJO_DEADLINE_INDEFINITE = static_cast(-1); +#else +#define MOJO_DEADLINE_INDEFINITE ((MojoDeadline) - 1) +#endif + +// |MojoHandleSignals|: Used to specify signals that can be watched for on a +// handle (and which can be triggered), e.g., the ability to read or write to +// the handle. +// |MOJO_HANDLE_SIGNAL_NONE| - No flags. A registered watch will always fail +// to arm with |MOJO_RESULT_FAILED_PRECONDITION| when watching for this. +// |MOJO_HANDLE_SIGNAL_READABLE| - Can read (e.g., a message) from the handle. +// |MOJO_HANDLE_SIGNAL_WRITABLE| - Can write (e.g., a message) to the handle. +// |MOJO_HANDLE_SIGNAL_PEER_CLOSED| - The peer handle is closed. +// |MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE| - Can read data from a data pipe +// consumer handle (implying MOJO_HANDLE_SIGNAL_READABLE is also set), +// AND there is some nonzero quantity of new data available on the pipe +// since the last |MojoReadData()| or |MojoBeginReadData()| call on the +// handle. + +typedef uint32_t MojoHandleSignals; + +#ifdef __cplusplus +const MojoHandleSignals MOJO_HANDLE_SIGNAL_NONE = 0; +const MojoHandleSignals MOJO_HANDLE_SIGNAL_READABLE = 1 << 0; +const MojoHandleSignals MOJO_HANDLE_SIGNAL_WRITABLE = 1 << 1; +const MojoHandleSignals MOJO_HANDLE_SIGNAL_PEER_CLOSED = 1 << 2; +const MojoHandleSignals MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE = 1 << 3; +#else +#define MOJO_HANDLE_SIGNAL_NONE ((MojoHandleSignals)0) +#define MOJO_HANDLE_SIGNAL_READABLE ((MojoHandleSignals)1 << 0) +#define MOJO_HANDLE_SIGNAL_WRITABLE ((MojoHandleSignals)1 << 1) +#define MOJO_HANDLE_SIGNAL_PEER_CLOSED ((MojoHandleSignals)1 << 2) +#define MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE ((MojoHandleSignals)1 << 3); +#endif + +// |MojoHandleSignalsState|: Returned by watch notification callbacks and +// |MojoQueryHandleSignalsState| functions to indicate the signaling state of +// handles. Members are as follows: +// - |satisfied signals|: Bitmask of signals that were satisfied at some time +// before the call returned. +// - |satisfiable signals|: These are the signals that are possible to +// satisfy. For example, if the return value was +// |MOJO_RESULT_FAILED_PRECONDITION|, you can use this field to +// determine which, if any, of the signals can still be satisfied. +// Note: This struct is not extensible (and only has 32-bit quantities), so it's +// 32-bit-aligned. +MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int32_t) == 4, "int32_t has weird alignment"); +struct MOJO_ALIGNAS(4) MojoHandleSignalsState { + MojoHandleSignals satisfied_signals; + MojoHandleSignals satisfiable_signals; +}; +MOJO_STATIC_ASSERT(sizeof(MojoHandleSignalsState) == 8, + "MojoHandleSignalsState has wrong size"); + +// |MojoWatcherNotificationFlags|: Passed to a callback invoked by a watcher +// when some observed signals are raised or a watched handle is closed. May take +// on any combination of the following values: +// +// |MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM| - The callback is being +// invoked as a result of a system-level event rather than a direct API +// call from user code. This may be used as an indication that user code +// is safe to call without fear of reentry. + +typedef uint32_t MojoWatcherNotificationFlags; + +#ifdef __cplusplus +const MojoWatcherNotificationFlags MOJO_WATCHER_NOTIFICATION_FLAG_NONE = 0; +const MojoWatcherNotificationFlags MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM = + 1 << 0; +#else +#define MOJO_WATCHER_NOTIFICATION_FLAG_NONE ((MojoWatcherNotificationFlags)0) +#define MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM \ + ((MojoWatcherNotificationFlags)1 << 0); +#endif + +// |MojoPropertyType|: Property types that can be passed to |MojoGetProperty()| +// to retrieve system properties. May take the following values: +// |MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED| - Whether making synchronous calls +// (i.e., blocking to wait for a response to an outbound message) is +// allowed. The property value is of boolean type. If the value is true, +// users should refrain from making sync calls. +typedef uint32_t MojoPropertyType; + +#ifdef __cplusplus +const MojoPropertyType MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED = 0; +#else +#define MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED ((MojoPropertyType)0) +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_TYPES_H_ diff --git a/mojo/public/c/system/watcher.h b/mojo/public/c/system/watcher.h new file mode 100644 index 0000000000000000000000000000000000000000..e32856b6d920d58b2e0fa121a5fb1fd683547e9c --- /dev/null +++ b/mojo/public/c/system/watcher.h @@ -0,0 +1,184 @@ +// 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. + +#ifndef MOJO_PUBLIC_C_SYSTEM_WATCHER_H_ +#define MOJO_PUBLIC_C_SYSTEM_WATCHER_H_ + +#include + +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// A callback used to notify watchers about events on their watched handles. +// +// See documentation for |MojoWatcherNotificationFlags| for details regarding +// the possible values of |flags|. +// +// See documentation for |MojoWatch()| for details regarding the other arguments +// this callback receives when called. +typedef void (*MojoWatcherCallback)(uintptr_t context, + MojoResult result, + struct MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags); + +// Creates a new watcher. +// +// Watchers are used to trigger arbitrary code execution when one or more +// handles change state to meet certain conditions. +// +// A newly registered watcher is initially disarmed and may be armed using +// |MojoArmWatcher()|. A watcher is also always disarmed immediately before any +// invocation of one or more notification callbacks in response to a single +// handle's state changing in some relevant way. +// +// Parameters: +// |callback|: The |MojoWatcherCallback| to invoke any time the watcher is +// notified of an event. See |MojoWatch()| for details regarding arguments +// passed to the callback. Note that this may be called from any arbitrary +// thread. +// |watcher_handle|: The address at which to store the MojoHandle +// corresponding to the new watcher if successfully created. +// +// Returns: +// |MOJO_RESULT_OK| if the watcher has been successfully created. +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a handle could not be allocated for +// this watcher. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateWatcher(MojoWatcherCallback callback, + MojoHandle* watcher_handle); + +// Adds a watch to a watcher. This allows the watcher to fire notifications +// regarding state changes on the handle corresponding to the arguments given. +// +// Note that notifications for a given watch context are guaranteed to be +// mutually exclusive in execution: the callback will never be entered for a +// given context while another invocation of the callback is still executing for +// the same context. As a result it is generally a good idea to ensure that +// callbacks do as little work as necessary in order to process the +// notification. +// +// Parameters: +// |watcher_handle|: The watcher to which |handle| is to be added. +// |handle|: The handle to add to the watcher. +// |signals|: The signals to watch for on |handle|. +// |context|: An arbitrary context value given to any invocation of the +// watcher's callback when invoked as a result of some state change +// relevant to this combination of |handle| and |signals|. Must be +// unique within any given watcher. +// +// Callback parameters (see |MojoWatcherNotificationCallback| above): +// When the watcher invokes its callback as a result of some notification +// relevant to this watch operation, |context| receives the value given here +// and |signals_state| receives the last known signals state of this handle. +// +// |result| is one of the following: +// |MOJO_RESULT_OK| if at least one of the watched signals is satisfied. The +// watcher must be armed for this notification to fire. +// |MOJO_RESULT_FAILED_PRECONDITION| if all of the watched signals are +// permanently unsatisfiable. The watcher must be armed for this +// notification to fire. +// |MOJO_RESULT_CANCELLED| if the watch has been cancelled. The may occur if +// the watcher has been closed, the watched handle has been closed, or +// the watch for |context| has been explicitly cancelled. This is always +// the last result received for any given context, and it is guaranteed +// to be received exactly once per watch, regardless of how the watch +// was cancelled. +// +// Returns: +// |MOJO_RESULT_OK| if the handle is now being watched by the watcher. +// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a watcher handle, +// |handle| is not a valid message pipe or data pipe handle. +// |MOJO_RESULT_ALREADY_EXISTS| if the watcher already has a watch registered +// for the given value of |context| or for the given |handle|. +MOJO_SYSTEM_EXPORT MojoResult MojoWatch(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context); + +// Removes a watch from a watcher. +// +// This ensures that the watch is cancelled as soon as possible. Cancellation +// may be deferred (or may even block) an aritrarily long time if the watch is +// already dispatching one or more notifications. +// +// When cancellation is complete, the watcher's callback is invoked one final +// time for |context|, with the result |MOJO_RESULT_CANCELLED|. +// +// The same behavior can be elicted by either closing the watched handle +// associated with this context, or by closing |watcher_handle| itself. In the +// lastter case, all registered contexts on the watcher are implicitly cancelled +// in a similar fashion. +// +// Parameters: +// |watcher_handle|: The handle of the watcher from which to remove a watch. +// |context|: The context of the watch to be removed. +// +// Returns: +// |MOJO_RESULT_OK| if the watch has been cancelled. +// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a watcher handle. +// |MOJO_RESULT_NOT_FOUND| if there is no watch registered on this watcher for +// the given value of |context|. +MOJO_SYSTEM_EXPORT MojoResult MojoCancelWatch(MojoHandle watcher_handle, + uintptr_t context); + +// Arms a watcher, enabling a single future event on one of the watched handles +// to trigger a single notification for each relevant watch context associated +// with that handle. +// +// Parameters: +// |watcher_handle|: The handle of the watcher. +// |num_ready_contexts|: An address pointing to the number of elements +// available for storage in the remaining output buffers. Optional and +// only used on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below for +// more details. +// |ready_contexts|: An output buffer for contexts corresponding to the +// watches which would have notified if the watcher were armed. Optional +// and only uesd on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below +// for more details. +// |ready_results|: An output buffer for MojoResult values corresponding to +// each context in |ready_contexts|. Optional and only used on failure. +// See |MOJO_RESULT_FAILED_PRECONDITION| below for more details. +// |ready_signals_states|: An output buffer for |MojoHandleSignalsState| +// structures corresponding to each context in |ready_contexts|. Optional +// and only used on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below +// for more details. +// +// Returns: +// |MOJO_RESULT_OK| if the watcher has been successfully armed. All arguments +// other than |watcher_handle| are ignored in this case. +// |MOJO_RESULT_NOT_FOUND| if the watcher does not have any registered watch +// contexts. All arguments other than |watcher_handle| are ignored in this +// case. +// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a valid watcher +// handle, or if |num_ready_contexts| is non-null but any of the output +// buffer paramters is null. +// |MOJO_RESULT_FAILED_PRECONDITION| if one or more watches would have +// notified immediately upon arming the watcher. If |num_handles| is +// non-null, this assumes there is enough space for |*num_handles| entries +// in each of the subsequent output buffer arguments. +// +// At most that many entries are placed in the output buffers, +// corresponding to the watches which would have signalled if the watcher +// had been armed successfully. The actual number of entries placed in the +// output buffers is written to |*num_ready_contexts| before returning. +// +// If more than (input) |*num_ready_contexts| watch contexts were ready to +// notify, the subset presented in output buffers is arbitrary, but the +// implementation makes a best effort to circulate the outputs across +// consecutive calls so that callers may reliably avoid handle starvation. +MOJO_SYSTEM_EXPORT MojoResult +MojoArmWatcher(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + struct MojoHandleSignalsState* ready_signals_states); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_WATCHER_H_ diff --git a/mojo/public/c/test_support/BUILD.gn b/mojo/public/c/test_support/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..e2abd5807eb99ebe6bbf87ecef8a0c644ba943a6 --- /dev/null +++ b/mojo/public/c/test_support/BUILD.gn @@ -0,0 +1,15 @@ +# Copyright 2014 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. + +static_library("test_support") { + output_name = "mojo_public_test_support" + + sources = [ + "test_support.h", + + # TODO(vtl): Convert this to thunks http://crbug.com/386799 + "../../tests/test_support_private.cc", + "../../tests/test_support_private.h", + ] +} diff --git a/mojo/public/c/test_support/test_support.h b/mojo/public/c/test_support/test_support.h new file mode 100644 index 0000000000000000000000000000000000000000..8e50441d68ccd5053bdc01b035e3057dc2a957fc --- /dev/null +++ b/mojo/public/c/test_support/test_support.h @@ -0,0 +1,52 @@ +// Copyright 2014 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 MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_ +#define MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_ + +// Note: This header should be compilable as C. + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// |sub_test_name| is optional. If not null, it usually describes one particular +// configuration of the test. For example, if |test_name| is "TestPacketRate", +// |sub_test_name| could be "100BytesPerPacket". +// When the perf data is visualized by the performance dashboard, data with +// different |sub_test_name|s (but the same |test_name|) are depicted as +// different traces on the same chart. +void MojoTestSupportLogPerfResult( + const char* test_name, + const char* sub_test_name, + double value, + const char* units); + +// Opens a "/"-delimited file path relative to the source root. +FILE* MojoTestSupportOpenSourceRootRelativeFile( + const char* source_root_relative_path); + +// Enumerates a "/"-delimited directory path relative to the source root. +// Returns only regular files. The return value is a heap-allocated array of +// heap-allocated strings. Each must be free'd separately. +// +// The return value is built like so: +// +// char** rv = (char**) calloc(N + 1, sizeof(char*)); +// rv[0] = strdup("a"); +// rv[1] = strdup("b"); +// rv[2] = strdup("c"); +// ... +// rv[N] = NULL; +// +char** MojoTestSupportEnumerateSourceRootRelativeDirectory( + const char* source_root_relative_path); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_ diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..bd87965fc85ac0a53bc4fd7721846e7f42cd9ae8 --- /dev/null +++ b/mojo/public/cpp/bindings/BUILD.gn @@ -0,0 +1,194 @@ +# Copyright 2014 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. + +interfaces_bindings_gen_dir = "$root_gen_dir/mojo/public/interfaces/bindings" + +component("bindings") { + sources = [ + # Normally, targets should depend on the source_sets generated by mojom + # targets. However, the generated source_sets use portions of the bindings + # library. In order to avoid linker warnings about locally-defined imports + # in Windows components build, this target depends on the generated C++ + # files directly so that the EXPORT macro defintions match. + "$interfaces_bindings_gen_dir/interface_control_messages.mojom-shared-internal.h", + "$interfaces_bindings_gen_dir/interface_control_messages.mojom-shared.cc", + "$interfaces_bindings_gen_dir/interface_control_messages.mojom-shared.h", + "$interfaces_bindings_gen_dir/interface_control_messages.mojom.cc", + "$interfaces_bindings_gen_dir/interface_control_messages.mojom.h", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom-shared-internal.h", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom-shared.cc", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom-shared.h", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.cc", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.h", + "array_data_view.h", + "array_traits.h", + "array_traits_carray.h", + "array_traits_stl.h", + "associated_binding.h", + "associated_binding_set.h", + "associated_group.h", + "associated_group_controller.h", + "associated_interface_ptr.h", + "associated_interface_ptr_info.h", + "associated_interface_request.h", + "binding.h", + "binding_set.h", + "bindings_export.h", + "clone_traits.h", + "connection_error_callback.h", + "connector.h", + "disconnect_reason.h", + "filter_chain.h", + "interface_data_view.h", + "interface_endpoint_client.h", + "interface_endpoint_controller.h", + "interface_id.h", + "interface_ptr.h", + "interface_ptr_info.h", + "interface_ptr_set.h", + "interface_request.h", + "lib/array_internal.cc", + "lib/array_internal.h", + "lib/array_serialization.h", + "lib/associated_binding.cc", + "lib/associated_group.cc", + "lib/associated_group_controller.cc", + "lib/associated_interface_ptr.cc", + "lib/associated_interface_ptr_state.h", + "lib/binding_state.cc", + "lib/binding_state.h", + "lib/bindings_internal.h", + "lib/buffer.h", + "lib/connector.cc", + "lib/control_message_handler.cc", + "lib/control_message_handler.h", + "lib/control_message_proxy.cc", + "lib/control_message_proxy.h", + "lib/equals_traits.h", + "lib/filter_chain.cc", + "lib/fixed_buffer.cc", + "lib/fixed_buffer.h", + "lib/handle_interface_serialization.h", + "lib/hash_util.h", + "lib/interface_endpoint_client.cc", + "lib/interface_ptr_state.h", + "lib/map_data_internal.h", + "lib/map_serialization.h", + "lib/may_auto_lock.h", + "lib/message.cc", + "lib/message_buffer.cc", + "lib/message_buffer.h", + "lib/message_builder.cc", + "lib/message_builder.h", + "lib/message_header_validator.cc", + "lib/message_internal.h", + "lib/multiplex_router.cc", + "lib/multiplex_router.h", + "lib/native_enum_data.h", + "lib/native_enum_serialization.h", + "lib/native_struct.cc", + "lib/native_struct_data.cc", + "lib/native_struct_data.h", + "lib/native_struct_serialization.cc", + "lib/native_struct_serialization.h", + "lib/pipe_control_message_handler.cc", + "lib/pipe_control_message_proxy.cc", + "lib/scoped_interface_endpoint_handle.cc", + "lib/serialization.h", + "lib/serialization_context.cc", + "lib/serialization_context.h", + "lib/serialization_forward.h", + "lib/serialization_util.h", + "lib/string_serialization.h", + "lib/string_traits_string16.cc", + "lib/sync_call_restrictions.cc", + "lib/sync_event_watcher.cc", + "lib/sync_handle_registry.cc", + "lib/sync_handle_watcher.cc", + "lib/template_util.h", + "lib/union_accessor.h", + "lib/validate_params.h", + "lib/validation_context.cc", + "lib/validation_context.h", + "lib/validation_errors.cc", + "lib/validation_errors.h", + "lib/validation_util.cc", + "lib/validation_util.h", + "map.h", + "map_data_view.h", + "map_traits.h", + "map_traits_stl.h", + "message.h", + "message_header_validator.h", + "native_enum.h", + "native_struct.h", + "native_struct_data_view.h", + "pipe_control_message_handler.h", + "pipe_control_message_handler_delegate.h", + "pipe_control_message_proxy.h", + "raw_ptr_impl_ref_traits.h", + "scoped_interface_endpoint_handle.h", + "string_data_view.h", + "string_traits.h", + "string_traits_stl.h", + "string_traits_string16.h", + "string_traits_string_piece.h", + "strong_associated_binding.h", + "strong_binding.h", + "strong_binding_set.h", + "struct_ptr.h", + "sync_call_restrictions.h", + "sync_event_watcher.h", + "sync_handle_registry.h", + "sync_handle_watcher.h", + "thread_safe_interface_ptr.h", + "type_converter.h", + "union_traits.h", + "unique_ptr_impl_ref_traits.h", + ] + + public_deps = [ + ":struct_traits", + "//base", + "//ipc:param_traits", + "//mojo/public/cpp/system", + ] + + deps = [ + "//base", + "//mojo/public/interfaces/bindings:bindings__generator", + "//mojo/public/interfaces/bindings:bindings_shared__generator", + ] + + defines = [ "MOJO_CPP_BINDINGS_IMPLEMENTATION" ] +} + +source_set("struct_traits") { + sources = [ + "enum_traits.h", + "struct_traits.h", + ] +} + +if (!is_ios) { + # TODO(yzshen): crbug.com/617718 Consider moving this into blink. + source_set("wtf_support") { + sources = [ + "array_traits_wtf_vector.h", + "lib/string_traits_wtf.cc", + "lib/wtf_clone_equals_util.h", + "lib/wtf_hash_util.h", + "lib/wtf_serialization.h", + "map_traits_wtf_hash_map.h", + "string_traits_wtf.h", + ] + + public_deps = [ + ":bindings", + "//third_party/WebKit/Source/wtf", + ] + + public_configs = [ "//third_party/WebKit/Source:config" ] + } +} diff --git a/mojo/public/cpp/bindings/DEPS b/mojo/public/cpp/bindings/DEPS new file mode 100644 index 0000000000000000000000000000000000000000..36eba448e8dc3184f841174b76107cae2b81a5b5 --- /dev/null +++ b/mojo/public/cpp/bindings/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+third_party/WebKit/Source/wtf", +] diff --git a/mojo/public/cpp/bindings/README.md b/mojo/public/cpp/bindings/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b37267a338f5a2337c3752fef1f25d1096226c5b --- /dev/null +++ b/mojo/public/cpp/bindings/README.md @@ -0,0 +1,1231 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C++ Bindings API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview +The Mojo C++ Bindings API leverages the +[C++ System API](/mojo/public/cpp/system) to provide a more natural set of +primitives for communicating over Mojo message pipes. Combined with generated +code from the [Mojom IDL and bindings generator](/mojo/public/tools/bindings), +users can easily connect interface clients and implementations across arbitrary +intra- and inter-process bounaries. + +This document provides a detailed guide to bindings API usage with example code +snippets. For a detailed API references please consult the headers in +[//mojo/public/cpp/bindings](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/). + +## Getting Started + +When a Mojom IDL file is processed by the bindings generator, C++ code is +emitted in a series of `.h` and `.cc` files with names based on the input +`.mojom` file. Suppose we create the following Mojom file at +`//services/db/public/interfaces/db.mojom`: + +``` +module db.mojom; + +interface Table { + AddRow(int32 key, string data); +}; + +interface Database { + CreateTable(Table& table); +}; +``` + +And a GN target to generate the bindings in +`//services/db/public/interfaces/BUILD.gn`: + +``` +import("//mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "db.mojom", + ] +} +``` + +If we then build this target: + +``` +ninja -C out/r services/db/public/interfaces +``` + +This will produce several generated source files, some of which are relevant to +C++ bindings. Two of these files are: + +``` +out/gen/services/business/public/interfaces/factory.mojom.cc +out/gen/services/business/public/interfaces/factory.mojom.h +``` + +You can include the above generated header in your sources in order to use the +definitions therein: + +``` cpp +#include "services/business/public/interfaces/factory.mojom.h" + +class TableImpl : public db::mojom::Table { + // ... +}; +``` + +This document covers the different kinds of definitions generated by Mojom IDL +for C++ consumers and how they can effectively be used to communicate across +message pipes. + +*** note +**NOTE:** Using C++ bindings from within Blink code is typically subject to +special constraints which require the use of a different generated header. +For details, see [Blink Type Mapping](#Blink-Type-Mapping). +*** + +## Interfaces + +Mojom IDL interfaces are translated to corresponding C++ (pure virtual) class +interface definitions in the generated header, consisting of a single generated +method signature for each request message on the interface. Internally there is +also generated code for serialization and deserialization of messages, but this +detail is hidden from bindings consumers. + +### Basic Usage + +Let's consider a new `//sample/logger.mojom` to define a simple logging +interface which clients can use to log simple string messages: + +``` cpp +module sample.mojom; + +interface Logger { + Log(string message); +}; +``` + +Running this through the bindings generator will produce a `logging.mojom.h` +with the following definitions (modulo unimportant details): + +``` cpp +namespace sample { +namespace mojom { + +class Logger { + virtual ~Logger() {} + + virtual void Log(const std::string& message) = 0; +}; + +using LoggerPtr = mojo::InterfacePtr; +using LoggerRequest = mojo::InterfaceRequest; + +} // namespace mojom +} // namespace sample +``` + +Makes sense. Let's take a closer look at those type aliases at the end. + +### InterfacePtr and InterfaceRequest + +You will notice the type aliases for `LoggerPtr` and +`LoggerRequest` are using two of the most fundamental template types in the C++ +bindings library: **`InterfacePtr`** and **`InterfaceRequest`**. + +In the world of Mojo bindings libraries these are effectively strongly-typed +message pipe endpoints. If an `InterfacePtr` is bound to a message pipe +endpoint, it can be dereferenced to make calls on an opaque `T` interface. These +calls immediately serialize their arguments (using generated code) and write a +corresponding message to the pipe. + +An `InterfaceRequest` is essentially just a typed container to hold the other +end of an `InterfacePtr`'s pipe -- the receiving end -- until it can be +routed to some implementation which will **bind** it. The `InterfaceRequest` +doesn't actually *do* anything other than hold onto a pipe endpoint and carry +useful compile-time type information. + +![Diagram illustrating InterfacePtr and InterfaceRequest on either end of a message pipe](https://docs.google.com/drawings/d/17d5gvErbQ6DthEBMS7I1WhCh9bz0n12pvNjydzuRfTI/pub?w=600&h=100) + +So how do we create a strongly-typed message pipe? + +### Creating Interface Pipes + +One way to do this is by manually creating a pipe and binding each end: + +``` cpp +#include "sample/logger.mojom.h" + +mojo::MessagePipe pipe; +sample::mojom::LoggerPtr logger; +sample::mojom::LoggerRequest request; + +logger.Bind(sample::mojom::LoggerPtrInfo(std::move(pipe.handle0), 0u)); +request.Bind(std::move(pipe.handle1)); +``` + +That's pretty verbose, but the C++ Bindings library provides more convenient +ways to accomplish the same thing. [interface_request.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/interface_request.h) +defines a `MakeRequest` function: + +``` cpp +sample::mojom::LoggerPtr logger; +sample::mojom::LoggerRequest request = mojo::MakeRequest(&logger); +``` + +and the `InterfaceRequest` constructor can also take an explicit +`InterfacePtr*` output argument: + +``` cpp +sample::mojom::LoggerPtr logger; +sample::mojom::LoggerRequest request(&logger); +``` + +Both of these last two snippets are equivalent to the first one. + +*** note +**NOTE:** In the first example above you may notice usage of the `LoggerPtrInfo` +type, which is a generated alias for `mojo::InterfacePtrInfo`. This is +similar to an `InterfaceRequest` in that it merely holds onto a pipe handle +and cannot actually read or write messages on the pipe. Both this type and +`InterfaceRequest` are safe to move freely from thread to thread, whereas a +bound `InterfacePtr` is bound to a single thread. + +An `InterfacePtr` may be unbound by calling its `PassInterface()` method, +which returns a new `InterfacePtrInfo`. Conversely, an `InterfacePtr` may +bind (and thus take ownership of) an `InterfacePtrInfo` so that interface +calls can be made on the pipe. + +The thread-bound nature of `InterfacePtr` is necessary to support safe +dispatch of its [message responses](#Receiving-Responses) and +[connection error notifications](#Connection-Errors). +*** + +Once the `LoggerPtr` is bound we can immediately begin calling `Logger` +interface methods on it, which will immediately write messages into the pipe. +These messages will stay queued on the receiving end of the pipe until someone +binds to it and starts reading them. + +``` cpp +logger->Log("Hello!"); +``` + +This actually writes a `Log` message to the pipe. + +![Diagram illustrating a message traveling on a pipe from LoggerPtr to LoggerRequest](https://docs.google.com/a/google.com/drawings/d/1jWEc6jJIP2ed77Gg4JJ3EVC7hvnwcImNqQJywFwpT8g/pub?w=648&h=123) + +But as mentioned above, `InterfaceRequest` *doesn't actually do anything*, so +that message will just sit on the pipe forever. We need a way to read messages +off the other end of the pipe and dispatch them. We have to +**bind the interface request**. + +### Binding an Interface Request + +There are many different helper classes in the bindings library for binding the +receiving end of a message pipe. The most primitive among them is the aptly +named `mojo::Binding`. A `mojo::Binding` bridges an implementation of `T` +with a single bound message pipe endpoint (via a `mojo::InterfaceRequest`), +which it continuously watches for readability. + +Any time the bound pipe becomes readable, the `Binding` will schedule a task to +read, deserialize (using generated code), and dispatch all available messages to +the bound `T` implementation. Below is a sample implementation of the `Logger` +interface. Notice that the implementation itself owns a `mojo::Binding`. This is +a common pattern, since a bound implementation must outlive any `mojo::Binding` +which binds it. + +``` cpp +#include "base/logging.h" +#include "base/macros.h" +#include "sample/logger.mojom.h" + +class LoggerImpl : public sample::mojom::Logger { + public: + // NOTE: A common pattern for interface implementations which have one + // instance per client is to take an InterfaceRequest in the constructor. + + explicit LoggerImpl(sample::mojom::LoggerRequest request) + : binding_(this, std::move(request)) {} + ~Logger() override {} + + // sample::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + } + + private: + mojo::Binding binding_; + + DISALLOW_COPY_AND_ASSIGN(LoggerImpl); +}; +``` + +Now we can construct a `LoggerImpl` over our pending `LoggerRequest`, and the +previously queued `Log` message will be dispatched ASAP on the `LoggerImpl`'s +thread: + +``` cpp +LoggerImpl impl(std::move(request)); +``` + +The diagram below illustrates the following sequence of events, all set in +motion by the above line of code: + +1. The `LoggerImpl` constructor is called, passing the `LoggerRequest` along + to the `Binding`. +2. The `Binding` takes ownership of the `LoggerRequest`'s pipe endpoint and + begins watching it for readability. The pipe is readable immediately, so a + task is scheduled to read the pending `Log` message from the pipe ASAP. +3. The `Log` message is read and deserialized, causing the `Binding` to invoke + the `Logger::Log` implementation on its bound `LoggerImpl`. + +![Diagram illustrating the progression of binding a request, reading a pending message, and dispatching it](https://docs.google.com/drawings/d/1c73-PegT4lmjfHoxhWrHTQXRvzxgb0wdeBa35WBwZ3Q/pub?w=550&h=500) + +As a result, our implementation will eventually log the client's `"Hello!"` +message via `LOG(ERROR)`. + +*** note +**NOTE:** Messages will only be read and dispatched from a pipe as long as the +object which binds it (*i.e.* the `mojo::Binding` in the above example) remains +alive. +*** + +### Receiving Responses + +Some Mojom interface methods expect a response. Suppose we modify our `Logger` +interface so that the last logged line can be queried like so: + +``` cpp +module sample.mojom; + +interface Logger { + Log(string message); + GetTail() => (string message); +}; +``` + +The generated C++ interface will now look like: + +``` cpp +namespace sample { +namespace mojom { + +class Logger { + public: + virtual ~Logger() {} + + virtual void Log(const std::string& message) = 0; + + using GetTailCallback = base::Callback; + + virtual void GetTail(const GetTailCallback& callback) = 0; +} + +} // namespace mojom +} // namespace sample +``` + +As before, both clients and implementations of this interface use the same +signature for the `GetTail` method: implementations use the `callback` argument +to *respond* to the request, while clients pass a `callback` argument to +asynchronously `receive` the response. Here's an updated implementation: + +```cpp +class LoggerImpl : public sample::mojom::Logger { + public: + // NOTE: A common pattern for interface implementations which have one + // instance per client is to take an InterfaceRequest in the constructor. + + explicit LoggerImpl(sample::mojom::LoggerRequest request) + : binding_(this, std::move(request)) {} + ~Logger() override {} + + // sample::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + lines_.push_back(message); + } + + void GetTail(const GetTailCallback& callback) override { + callback.Run(lines_.back()); + } + + private: + mojo::Binding binding_; + std::vector lines_; + + DISALLOW_COPY_AND_ASSIGN(LoggerImpl); +}; +``` + +And an updated client call: + +``` cpp +void OnGetTail(const std::string& message) { + LOG(ERROR) << "Tail was: " << message; +} + +logger->GetTail(base::Bind(&OnGetTail)); +``` + +Behind the scenes, the implementation-side callback is actually serializing the +response arguments and writing them onto the pipe for delivery back to the +client. Meanwhile the client-side callback is invoked by some internal logic +which watches the pipe for an incoming response message, reads and deserializes +it once it arrives, and then invokes the callback with the deserialized +parameters. + +### Connection Errors + +If there are no remaining messages available on a pipe and the remote end has +been closed, a connection error will be triggered on the local end. Connection +errors may also be triggered by automatic forced local pipe closure due to +*e.g.* a validation error when processing a received message. + +Regardless of the underlying cause, when a connection error is encountered on +a binding endpoint, that endpoint's **connection error handler** (if set) is +invoked. This handler is a simple `base::Closure` and may only be invoked +*once* as long as the endpoint is bound to the same pipe. Typically clients and +implementations use this handler to do some kind of cleanup or -- particuarly if +the error was unexpected -- create a new pipe and attempt to establish a new +connection with it. + +All message pipe-binding C++ objects (*e.g.*, `mojo::Binding`, +`mojo::InterfacePtr`, *etc.*) support setting their connection error handler +via a `set_connection_error_handler` method. + +We can set up another end-to-end `Logger` example to demonstrate error handler +invocation: + +``` cpp +sample::mojom::LoggerPtr logger; +LoggerImpl impl(mojo::MakeRequest(&logger)); +impl.set_connection_error_handler(base::Bind([] { LOG(ERROR) << "Bye."; })); +logger->Log("OK cool"); +logger.reset(); // Closes the client end. +``` + +As long as `impl` stays alive here, it will eventually receive the `Log` message +followed immediately by an invocation of the bound callback which outputs +`"Bye."`. Like all other bindings callbacks, a connection error handler will +**never** be invoked once its corresponding binding object has been destroyed. + +In fact, suppose instead that `LoggerImpl` had set up the following error +handler within its constructor: + +``` cpp +LoggerImpl::LoggerImpl(sample::mojom::LoggerRequest request) + : binding_(this, std::move(request)) { + binding_.set_connection_error_handler( + base::Bind(&LoggerImpl::OnError, base::Unretained(this))); +} + +void LoggerImpl::OnError() { + LOG(ERROR) << "Client disconnected! Purging log lines."; + lines_.clear(); +} +``` + +The use of `base::Unretained` is *safe* because the error handler will never be +invoked beyond the lifetime of `binding_`, and `this` owns `binding_`. + +### A Note About Ordering + +As mentioned in the previous section, closing one end of a pipe will eventually +trigger a connection error on the other end. However it's important to note that +this event is itself ordered with respect to any other event (*e.g.* writing a +message) on the pipe. + +This means that it's safe to write something contrived like: + +``` cpp +void GoBindALogger(sample::mojom::LoggerRequest request) { + LoggerImpl impl(std::move(request)); + base::RunLoop loop; + impl.set_connection_error_handler(loop.QuitClosure()); + loop.Run(); +} + +void LogSomething() { + sample::mojom::LoggerPtr logger; + bg_thread->task_runner()->PostTask( + FROM_HERE, base::BindOnce(&GoBindALogger, mojo::MakeRequest(&logger))); + logger->Log("OK Computer"); +} +``` + +When `logger` goes out of scope it immediately closes its end of the message +pipe, but the impl-side won't notice this until it receives the sent `Log` +message. Thus the `impl` above will first log our message and *then* see a +connection error and break out of the run loop. + +### Sending Interfaces Over Interfaces + +Now we know how to create interface pipes and use their Ptr and Request +endpoints in some interesting ways. This still doesn't add up to interesting +IPC! The bread and butter of Mojo IPC is the ability to transfer interface +endpoints across other interfaces, so let's take a look at how to accomplish +that. + +#### Sending Interface Requests + +Consider a new example Mojom in `//sample/db.mojom`: + +``` cpp +module db.mojom; + +interface Table { + void AddRow(int32 key, string data); +}; + +interface Database { + AddTable(Table& table); +}; +``` + +As noted in the +[Mojom IDL documentation](/mojo/public/tools/bindings#Primitive-Types), +the `Table&` syntax denotes a `Table` interface request. This corresponds +precisely to the `InterfaceRequest` type discussed in the sections above, and +in fact the generated code for these interfaces is approximately: + +``` cpp +namespace db { +namespace mojom { + +class Table { + public: + virtual ~Table() {} + + virtual void AddRow(int32_t key, const std::string& data) = 0; +} + +using TablePtr = mojo::InterfacePtr; +using TableRequest = mojo::InterfaceRequest
; + +class Database { + public: + virtual ~Database() {} + + virtual void AddTable(TableRequest table); +}; + +using DatabasePtr = mojo::InterfacePtr; +using DatabaseRequest = mojo::InterfaceRequest; + +} // namespace mojom +} // namespace db +``` + +We can put this all together now with an implementation of `Table` and +`Database`: + +``` cpp +#include "sample/db.mojom.h" + +class TableImpl : public db::mojom:Table { + public: + explicit TableImpl(db::mojom::TableRequest request) + : binding_(this, std::move(request)) {} + ~TableImpl() override {} + + // db::mojom::Table: + void AddRow(int32_t key, const std::string& data) override { + rows_.insert({key, data}); + } + + private: + mojo::Binding binding_; + std::map rows_; +}; + +class DatabaseImpl : public db::mojom::Database { + public: + explicit DatabaseImpl(db::mojom::DatabaseRequest request) + : binding_(this, std::move(request)) {} + ~DatabaseImpl() override {} + + // db::mojom::Database: + void AddTable(db::mojom::TableRequest table) { + tables_.emplace_back(base::MakeUnique(std::move(table))); + } + + private: + mojo::Binding binding_; + std::vector> tables_; +}; +``` + +Pretty straightforward. The `Table&` Mojom paramter to `AddTable` translates to +a C++ `db::mojom::TableRequest`, aliased from +`mojo::InterfaceRequest`, which we know is just a +strongly-typed message pipe handle. When `DatabaseImpl` gets an `AddTable` call, +it constructs a new `TableImpl` and binds it to the received `TableRequest`. + +Let's see how this can be used. + +``` cpp +db::mojom::DatabasePtr database; +DatabaseImpl db_impl(mojo::MakeRequest(&database)); + +db::mojom::TablePtr table1, table2; +database->AddTable(mojo::MakeRequest(&table1)); +database->AddTable(mojo::MakeRequest(&table2)); + +table1->AddRow(1, "hiiiiiiii"); +table2->AddRow(2, "heyyyyyy"); +``` + +Notice that we can again start using the new `Table` pipes immediately, even +while their `TableRequest` endpoints are still in transit. + +#### Sending InterfacePtrs + +Of course we can also send `InterfacePtr`s: + +``` cpp +interface TableListener { + OnRowAdded(int32 key, string data); +}; + +interface Table { + AddRow(int32 key, string data); + + AddListener(TableListener listener); +}; +``` + +This would generate a `Table::AddListener` signature like so: + +``` cpp + virtual void AddListener(TableListenerPtr listener) = 0; +``` + +and this could be used like so: + +``` cpp +db::mojom::TableListenerPtr listener; +TableListenerImpl impl(mojo::MakeRequest(&listener)); +table->AddListener(std::move(listener)); +``` + +## Other Interface Binding Types + +The [Interfaces](#Interfaces) section above covers basic usage of the most +common bindings object types: `InterfacePtr`, `InterfaceRequest`, and `Binding`. +While these types are probably the most commonly used in practice, there are +several other ways of binding both client- and implementation-side interface +pipes. + +### Strong Bindings + +A **strong binding** exists as a standalone object which owns its interface +implementation and automatically cleans itself up when its bound interface +endpoint detects an error. The +[**`MakeStrongBinding`**](https://cs.chromim.org/chromium/src//mojo/public/cpp/bindings/strong_binding.h) +function is used to create such a binding. +. + +``` cpp +class LoggerImpl : public sample::mojom::Logger { + public: + LoggerImpl() {} + ~LoggerImpl() override {} + + // sample::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + } + + private: + // NOTE: This doesn't own any Binding object! +}; + +db::mojom::LoggerPtr logger; +mojo::MakeStrongBinding(base::MakeUnique(), + mojo::MakeRequest(&logger)); + +logger->Log("NOM NOM NOM MESSAGES"); +``` + +Now as long as `logger` remains open somewhere in the system, the bound +`DatabaseImpl` on the other end will remain alive. + +### Binding Sets + +Sometimes it's useful to share a single implementation instance with multiple +clients. [**`BindingSet`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/binding_set.h) +makes this easy. Consider the Mojom: + +``` cpp +module system.mojom; + +interface Logger { + Log(string message); +}; + +interface LoggerProvider { + GetLogger(Logger& logger); +}; +``` + +We can use `BindingSet` to bind multiple `Logger` requests to a single +implementation instance: + +``` cpp +class LogManager : public system::mojom::LoggerProvider, + public system::mojom::Logger { + public: + explicit LogManager(system::mojom::LoggerProviderRequest request) + : provider_binding_(this, std::move(request)) {} + ~LogManager() {} + + // system::mojom::LoggerProvider: + void GetLogger(LoggerRequest request) override { + logger_bindings_.AddBinding(this, std::move(request)); + } + + // system::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + } + + private: + mojo::Binding provider_binding_; + mojo::BindingSet logger_bindings_; +}; + +``` + + +### InterfacePtr Sets + +Similar to the `BindingSet` above, sometimes it's useful to maintain a set of +`InterfacePtr`s for *e.g.* a set of clients observing some event. +[**`InterfacePtrSet`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/interface_ptr_set.h) +is here to help. Take the Mojom: + +``` cpp +module db.mojom; + +interface TableListener { + OnRowAdded(int32 key, string data); +}; + +interface Table { + AddRow(int32 key, string data); + AddListener(TableListener listener); +}; +``` + +An implementation of `Table` might look something like like this: + +``` cpp +class TableImpl : public db::mojom::Table { + public: + TableImpl() {} + ~TableImpl() override {} + + // db::mojom::Table: + void AddRow(int32_t key, const std::string& data) override { + rows_.insert({key, data}); + listeners_.ForEach([key, &data](db::mojom::TableListener* listener) { + listener->OnRowAdded(key, data); + }); + } + + void AddListener(db::mojom::TableListenerPtr listener) { + listeners_.AddPtr(std::move(listener)); + } + + private: + mojo::InterfacePtrSet listeners_; + std::map rows_; +}; +``` + +## Associated Interfaces + +See [this document](https://www.chromium.org/developers/design-documents/mojo/associated-interfaces). + +TODO: Move the above doc into the repository markdown docs. + +## Synchronous Calls + +See [this document](https://www.chromium.org/developers/design-documents/mojo/synchronous-calls) + +TODO: Move the above doc into the repository markdown docs. + +## Type Mapping + +In many instances you might prefer that your generated C++ bindings use a more +natural type to represent certain Mojom types in your interface methods. For one +example consider a Mojom struct such as the `Rect` below: + +``` cpp +module gfx.mojom; + +struct Rect { + int32 x; + int32 y; + int32 width; + int32 height; +}; + +interface Canvas { + void FillRect(Rect rect); +}; +``` + +The `Canvas` Mojom interface would normally generate a C++ interface like: + +``` cpp +class Canvas { + public: + virtual void FillRect(RectPtr rect) = 0; +}; +``` + +However, the Chromium tree already defines a native +[`gfx::Rect`](https://cs.chromium.org/chromium/src/ui/gfx/geometry/rect.h) which +is equivalent in meaning but which also has useful helper methods. Instead of +manually converting between a `gfx::Rect` and the Mojom-generated `RectPtr` at +every message boundary, wouldn't it be nice if the Mojom bindings generator +could instead generate: + +``` cpp +class Canvas { + public: + virtual void FillRect(const gfx::Rect& rect) = 0; +} +``` + +The correct answer is, "Yes! That would be nice!" And fortunately, it can! + +### Global Configuration + +While this feature is quite powerful, it introduces some unavoidable complexity +into build system. This stems from the fact that type-mapping is an inherently +viral concept: if `gfx::mojom::Rect` is mapped to `gfx::Rect` anywhere, the +mapping needs to apply *everywhere*. + +For this reason we have a few global typemap configurations defined in +[chromium_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/chromium_bindings_configuration.gni) +and +[blink_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/blink_bindings_configuration.gni). These configure the two supported [variants](#Variants) of Mojom generated +bindings in the repository. Read more on this in the sections that follow. + +For now, let's take a look at how to express the mapping from `gfx::mojom::Rect` +to `gfx::Rect`. + +### Defining `StructTraits` + +In order to teach generated bindings code how to serialize an arbitrary native +type `T` as an arbitrary Mojom type `mojom::U`, we need to define an appropriate +specialization of the +[`mojo::StructTraits`](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/struct_traits.h) +template. + +A valid specialization of `StructTraits` MUST define the following static +methods: + +* A single static accessor for every field of the Mojom struct, with the exact + same name as the struct field. These accessors must all take a const ref to + an object of the native type, and must return a value compatible with the + Mojom struct field's type. This is used to safely and consistently extract + data from the native type during message serialization without incurring extra + copying costs. + +* A single static `Read` method which initializes an instance of the the native + type given a serialized representation of the Mojom struct. The `Read` method + must return a `bool` to indicate whether the incoming data is accepted + (`true`) or rejected (`false`). + +There are other methods a `StructTraits` specialization may define to satisfy +some less common requirements. See +[Advanced StructTraits Usage](#Advanced-StructTraits-Usage) for details. + +In order to define the mapping for `gfx::Rect`, we want the following +`StructTraits` specialization, which we'll define in +`//ui/gfx/geometry/mojo/geometry_struct_traits.h`: + +``` cpp +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/mojo/geometry.mojom.h" + +namespace mojo { + +template <> +class StructTraits { + public: + static int32_t x(const gfx::Rect& r) { return r.x(); } + static int32_t y(const gfx::Rect& r) { return r.y(); } + static int32_t width(const gfx::Rect& r) { return r.width(); } + static int32_t height(const gfx::Rect& r) { return r.height(); } + + static bool Read(gfx::mojom::RectDataView data, gfx::Rect* out_rect); +}; + +} // namespace mojo +``` + +And in `//ui/gfx/geometry/mojo/geometry_struct_traits.cc`: + +``` cpp +#include "ui/gfx/geometry/mojo/geometry_struct_traits.h" + +namespace mojo { + +// static +template <> +bool StructTraits::Read( + gfx::mojom::RectDataView data, + gfx::Rect* out_rect) { + if (data.width() < 0 || data.height() < 0) + return false; + + out_rect->SetRect(data.x(), data.y(), data.width(), data.height()); + return true; +}; + +} // namespace mojo +``` + +Note that the `Read()` method returns `false` if either the incoming `width` or +`height` fields are negative. This acts as a validation step during +deserialization: if a client sends a `gfx::Rect` with a negative width or +height, its message will be rejected and the pipe will be closed. In this way, +type mapping can serve to enable custom validation logic in addition to making +callsites and interface implemention more convenient. + +### Enabling a New Type Mapping + +We've defined the `StructTraits` necessary, but we still need to teach the +bindings generator (and hence the build system) about the mapping. To do this we +must create a **typemap** file, which uses familiar GN syntax to describe the +new type mapping. + +Let's place this `geometry.typemap` file alongside our Mojom file: + +``` +mojom = "//ui/gfx/geometry/mojo/geometry.mojom" +public_headers = [ "//ui/gfx/geometry/rect.h" ] +traits_headers = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.h" ] +sources = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.cc" ] +public_deps = [ "//ui/gfx/geometry" ] +type_mappings = [ + "gfx.mojom.Rect=gfx::Rect", +] +``` + +Let's look at each of the variables above: + +* `mojom`: Specifies the `mojom` file to which the typemap applies. Many + typemaps may apply to the same `mojom` file, but any given typemap may only + apply to a single `mojom` file. +* `public_headers`: Additional headers required by any code which would depend + on the Mojom definition of `gfx.mojom.Rect` now that the typemap is applied. + Any headers required for the native target type definition should be listed + here. +* `traits_headers`: Headers which contain the relevant `StructTraits` + specialization(s) for any type mappings described by this file. +* `sources`: Any private implementation sources needed for the `StructTraits` + definition. +* `public_deps`: Target dependencies exposed by the `public_headers` and + `traits_headers`. +* `deps`: Target dependencies exposed by `sources` but not already covered by + `public_deps`. +* `type_mappings`: A list of type mappings to be applied for this typemap. The + strings in this list are of the format `"MojomType=CppType"`, where + `MojomType` must be a fully qualified Mojom typename and `CppType` must be a + fully qualified C++ typename. Additional attributes may be specified in square + brackets following the `CppType`: + * `move_only`: The `CppType` is move-only and should be passed by value + in any generated method signatures. Note that `move_only` is transitive, + so containers of `MojomType` will translate to containers of `CppType` + also passed by value. + * `copyable_pass_by_value`: Forces values of type `CppType` to be passed by + value without moving them. Unlike `move_only`, this is not transitive. + * `nullable_is_same_type`: By default a non-nullable `MojomType` will be + mapped to `CppType` while a nullable `MojomType?` will be mapped to + `base::Optional`. If this attribute is set, the `base::Optional` + wrapper is omitted for nullable `MojomType?` values, but the + `StructTraits` definition for this type mapping must define additional + `IsNull` and `SetToNull` methods. See + [Specializing Nullability](#Specializing-Nullability) below. + + +Now that we have the typemap file we need to add it to a local list of typemaps +that can be added to the global configuration. We create a new +`//ui/gfx/typemaps.gni` file with the following contents: + +``` +typemaps = [ + "//ui/gfx/geometry/mojo/geometry.typemap", +] +``` + +And finally we can reference this file in the global default (Chromium) bindings +configuration by adding it to `_typemap_imports` in +[chromium_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/chromium_bindings_configuration.gni): + +``` +_typemap_imports = [ + ..., + "//ui/gfx/typemaps.gni", + ..., +] +``` + +### StructTraits Reference + +Each of a `StructTraits` specialization's static getter methods -- one per +struct field -- must return a type which can be used as a data source for the +field during serialization. This is a quick reference mapping Mojom field type +to valid getter return types: + +| Mojom Field Type | C++ Getter Return Type | +|------------------------------|------------------------| +| `bool` | `bool` +| `int8` | `int8_t` +| `uint8` | `uint8_t` +| `int16` | `int16_t` +| `uint16` | `uint16_t` +| `int32` | `int32_t` +| `uint32` | `uint32_t` +| `int64` | `int64_t` +| `uint64` | `uint64_t` +| `float` | `float` +| `double` | `double` +| `handle` | `mojo::ScopedHandle` +| `handle` | `mojo::ScopedMessagePipeHandle` +| `handle` | `mojo::ScopedDataPipeConsumerHandle` +| `handle` | `mojo::ScopedDataPipeProducerHandle` +| `handle` | `mojo::ScopedSharedBufferHandle` +| `FooInterface` | `FooInterfacePtr` +| `FooInterface&` | `FooInterfaceRequest` +| `associated FooInterface` | `FooAssociatedInterfacePtr` +| `associated FooInterface&` | `FooAssociatedInterfaceRequest` +| `string` | Value or reference to any type `T` that has a `mojo::StringTraits` specialization defined. By default this includes `std::string`, `base::StringPiece`, and `WTF::String` (Blink). +| `array` | Value or reference to any type `T` that has a `mojo::ArrayTraits` specialization defined. By default this includes `std::vector`, `mojo::CArray`, and `WTF::Vector` (Blink). +| `map` | Value or reference to any type `T` that has a `mojo::MapTraits` specialization defined. By default this includes `std::map`, `mojo::unordered_map`, and `WTF::HashMap` (Blink). +| `FooEnum` | Value of any type that has an appropriate `EnumTraits` specialization defined. By default this inlcudes only the generated `FooEnum` type. +| `FooStruct` | Value or reference to any type that has an appropriate `StructTraits` specialization defined. By default this includes only the generated `FooStructPtr` type. +| `FooUnion` | Value of reference to any type that has an appropriate `UnionTraits` specialization defined. By default this includes only the generated `FooUnionPtr` type. + +### Using Generated DataView Types + +Static `Read` methods on `StructTraits` specializations get a generated +`FooDataView` argument (such as the `RectDataView` in the example above) which +exposes a direct view of the serialized Mojom structure within an incoming +message's contents. In order to make this as easy to work with as possible, the +generated `FooDataView` types have a generated method corresponding to every +struct field: + +* For POD field types (*e.g.* bools, floats, integers) these are simple accessor + methods with names identical to the field name. Hence in the `Rect` example we + can access things like `data.x()` and `data.width()`. The return types + correspond exactly to the mappings listed in the table above, under + [StructTraits Reference](#StructTraits-Reference). + +* For handle and interface types (*e.g* `handle` or `FooInterface&`) these + are named `TakeFieldName` (for a field named `field_name`) and they return an + appropriate move-only handle type by value. The return types correspond + exactly to the mappings listed in the table above, under + [StructTraits Reference](#StructTraits-Reference). + +* For all other field types (*e.g.*, enums, strings, arrays, maps, structs) + these are named `ReadFieldName` (for a field named `field_name`) and they + return a `bool` (to indicate success or failure in reading). On success they + fill their output argument with the deserialized field value. The output + argument may be a pointer to any type with an appropriate `StructTraits` + specialization defined, as mentioned in the table above, under + [StructTraits Reference](#StructTraits-Reference). + +An example would be useful here. Suppose we introduced a new Mojom struct: + +``` cpp +struct RectPair { + Rect left; + Rect right; +}; +``` + +and a corresponding C++ type: + +``` cpp +class RectPair { + public: + RectPair() {} + + const gfx::Rect& left() const { return left_; } + const gfx::Rect& right() const { return right_; } + + void Set(const gfx::Rect& left, const gfx::Rect& right) { + left_ = left; + right_ = right; + } + + // ... some other stuff + + private: + gfx::Rect left_; + gfx::Rect right_; +}; +``` + +Our traits to map `gfx::mojom::RectPair` to `gfx::RectPair` might look like +this: + +``` cpp +namespace mojo { + +template <> +class StructTraits + public: + static const gfx::Rect& left(const gfx::RectPair& pair) { + return pair.left(); + } + + static const gfx::Rect& right(const gfx::RectPair& pair) { + return pair.right(); + } + + static bool Read(gfx::mojom::RectPairDataView data, gfx::RectPair* out_pair) { + gfx::Rect left, right; + if (!data.ReadLeft(&left) || !data.ReadRight(&right)) + return false; + out_pair->Set(left, right); + return true; + } +} // namespace mojo +``` + +Generated `ReadFoo` methods always convert `multi_word_field_name` fields to +`ReadMultiWordFieldName` methods. + +### Variants + +By now you may have noticed that additional C++ sources are generated when a +Mojom is processed. These exist due to type mapping, and the source files we +refer to throughout this docuemnt (namely `foo.mojom.cc` and `foo.mojom.h`) are +really only one **variant** (the *default* or *chromium* variant) of the C++ +bindings for a given Mojom file. + +The only other variant currently defined in the tree is the *blink* variant, +which produces a few additional files: + +``` +out/gen/sample/db.mojom-blink.cc +out/gen/sample/db.mojom-blink.h +``` + +These files mirror the definitions in the default variant but with different +C++ types in place of certain builtin field and parameter types. For example, +Mojom strings are represented by `WTF::String` instead of `std::string`. To +avoid symbol collisions, the variant's symbols are nested in an extra inner +namespace, so Blink consumer of the interface might write something like: + +``` +#include "sample/db.mojom-blink.h" + +class TableImpl : public db::mojom::blink::Table { + public: + void AddRow(int32_t key, const WTF::String& data) override { + // ... + } +}; +``` + +In addition to using different C++ types for builtin strings, arrays, and maps, +the global typemap configuration for default and "blink" variants are completely +separate. To add a typemap for the Blink configuration, you can modify +[blink_bindings_configuration.gni](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings/blink_bindings_configuration.gni). + +All variants share some definitions which are unaffected by differences in the +type mapping configuration (enums, for example). These definitions are generated +in *shared* sources: + +``` +out/gen/sample/db.mojom-shared.cc +out/gen/sample/db.mojom-shared.h +out/gen/sample/db.mojom-shared-internal.h +``` + +Including either variant's header (`db.mojom.h` or `db.mojom-blink.h`) +implicitly includes the shared header, but you have on some occasions wish to +include *only* the shared header in some instances. + +Finally, note that for `mojom` GN targets, there is implicitly a corresponding +`mojom_{variant}` target defined for any supported bindings configuration. So +for example if you've defined in `//sample/BUILD.gn`: + +``` +import("mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "db.mojom", + ] +} +``` + +Code in Blink which wishes to use the generated Blink-variant definitions must +depend on `"//sample:interfaces_blink"`. + +## Versioning Considerations + +For general documentation of versioning in the Mojom IDL see +[Versioning](/mojo/public/tools/bindings#Versioning). + +This section briefly discusses some C++-specific considerations relevant to +versioned Mojom types. + +### Querying Interface Versions + +`InterfacePtr` defines the following methods to query or assert remote interface +version: + +```cpp +void QueryVersion(const base::Callback& callback); +``` + +This queries the remote endpoint for the version number of its binding. When a +response is received `callback` is invoked with the remote version number. Note +that this value is cached by the `InterfacePtr` instance to avoid redundant +queries. + +```cpp +void RequireVersion(uint32_t version); +``` + +Informs the remote endpoint that a minimum version of `version` is required by +the client. If the remote endpoint cannot support that version, it will close +its end of the pipe immediately, preventing any other requests from being +received. + +### Versioned Enums + +For convenience, every extensible enum has a generated helper function to +determine whether a received enum value is known by the implementation's current +version of the enum definition. For example: + +```cpp +[Extensible] +enum Department { + SALES, + DEV, + RESEARCH, +}; +``` + +generates the function in the same namespace as the generated C++ enum type: + +```cpp +inline bool IsKnownEnumValue(Department value); +``` + +### Additional Documentation + +[Calling Mojo From Blink](https://www.chromium.org/developers/design-documents/mojo/calling-mojo-from-blink) +: A brief overview of what it looks like to use Mojom C++ bindings from + within Blink code. diff --git a/mojo/public/cpp/bindings/array_data_view.h b/mojo/public/cpp/bindings/array_data_view.h new file mode 100644 index 0000000000000000000000000000000000000000..d02a8846ec745b89edc15f6b8a1e204a781f17f9 --- /dev/null +++ b/mojo/public/cpp/bindings/array_data_view.h @@ -0,0 +1,244 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_ARRAY_DATA_VIEW_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_DATA_VIEW_H_ + +#include + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" + +namespace mojo { +namespace internal { + +template +class ArrayDataViewImpl; + +template +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + T operator[](size_t index) const { return data_->at(index); } + + const T* data() const { return data_->storage(); } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + bool operator[](size_t index) const { return data_->at(index); } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + static_assert(sizeof(T) == sizeof(int32_t), "Unexpected enum size"); + + using Data_ = typename MojomTypeTraits>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + T operator[](size_t index) const { return static_cast(data_->at(index)); } + + const T* data() const { return reinterpret_cast(data_->storage()); } + + template + bool Read(size_t index, U* output) { + return Deserialize(data_->at(index), output); + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + template + U Take(size_t index) { + U result; + bool ret = Deserialize(&data_->at(index), &result, context_); + DCHECK(ret); + return result; + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + T Take(size_t index) { + T result; + bool ret = Deserialize(&data_->at(index), &result, context_); + DCHECK(ret); + return result; + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template +class ArrayDataViewImpl::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + void GetDataView(size_t index, T* output) { + *output = T(data_->at(index).Get(), context_); + } + + template + bool Read(size_t index, U* output) { + return Deserialize(data_->at(index).Get(), output, context_); + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + void GetDataView(size_t index, T* output) { + *output = T(&data_->at(index), context_); + } + + template + bool Read(size_t index, U* output) { + return Deserialize(&data_->at(index), output, context_); + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +} // namespace internal + +template +class MapDataView; + +template +class ArrayDataView : public internal::ArrayDataViewImpl { + public: + using Element = T; + using Data_ = typename internal::ArrayDataViewImpl::Data_; + + ArrayDataView() : internal::ArrayDataViewImpl(nullptr, nullptr) {} + + ArrayDataView(Data_* data, internal::SerializationContext* context) + : internal::ArrayDataViewImpl(data, context) {} + + bool is_null() const { return !this->data_; } + + size_t size() const { return this->data_->size(); } + + // Methods to access elements are different for different element types. They + // are inherited from internal::ArrayDataViewImpl: + + // POD types except boolean and enums: + // T operator[](size_t index) const; + // const T* data() const; + + // Boolean: + // bool operator[](size_t index) const; + + // Enums: + // T operator[](size_t index) const; + // const T* data() const; + // template + // bool Read(size_t index, U* output); + + // Handles: + // T Take(size_t index); + + // Interfaces: + // template + // U Take(size_t index); + + // Object types: + // void GetDataView(size_t index, T* output); + // template + // bool Read(size_t index, U* output); + + private: + template + friend class MapDataView; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_DATA_VIEW_H_ diff --git a/mojo/public/cpp/bindings/array_traits.h b/mojo/public/cpp/bindings/array_traits.h new file mode 100644 index 0000000000000000000000000000000000000000..594b2e0789d000c50ac8b5364d7cce83151b98b7 --- /dev/null +++ b/mojo/public/cpp/bindings/array_traits.h @@ -0,0 +1,71 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_H_ + +namespace mojo { + +// This must be specialized for any type |T| to be serialized/deserialized as +// a mojom array. +// +// Usually you would like to do a partial specialization for a container (e.g. +// vector) template. Imagine you want to specialize it for Container<>, you need +// to implement: +// +// template +// struct ArrayTraits> { +// using Element = T; +// // These two statements are optional. Use them if you'd like to serialize +// // a container that supports iterators but does not support O(1) random +// // access and so GetAt(...) would be expensive. +// // using Iterator = T::iterator; +// // using ConstIterator = T::const_iterator; +// +// // These two methods are optional. Please see comments in struct_traits.h +// static bool IsNull(const Container& input); +// static void SetToNull(Container* output); +// +// static size_t GetSize(const Container& input); +// +// // These two methods are optional. They are used to access the +// // underlying storage of the array to speed up copy of POD types. +// static T* GetData(Container& input); +// static const T* GetData(const Container& input); +// +// // The following six methods are optional if the GetAt(...) methods are +// // implemented. These methods specify how to read the elements of +// // Container in some sequential order specified by the iterator. +// // +// // Acquires an iterator positioned at the first element in the container. +// static ConstIterator GetBegin(const Container& input); +// static Iterator GetBegin(Container& input); +// +// // Advances |iterator| to the next position within the container. +// static void AdvanceIterator(ConstIterator& iterator); +// static void AdvanceIterator(Iterator& iterator); +// +// // Returns a reference to the value at the current position of +// // |iterator|. Optionally, the ConstIterator version of GetValue can +// // return by value instead of by reference if it makes sense for the +// // type. +// static const T& GetValue(ConstIterator& iterator); +// static T& GetValue(Iterator& iterator); +// +// // These two methods are optional if the iterator methods are +// // implemented. +// static T& GetAt(Container& input, size_t index); +// static const T& GetAt(const Container& input, size_t index); +// +// // Returning false results in deserialization failure and causes the +// // message pipe receiving it to be disconnected. +// static bool Resize(Container& input, size_t size); +// }; +// +template +struct ArrayTraits; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/array_traits_carray.h b/mojo/public/cpp/bindings/array_traits_carray.h new file mode 100644 index 0000000000000000000000000000000000000000..3ff694b88217532cf21e25cc36523148059589ac --- /dev/null +++ b/mojo/public/cpp/bindings/array_traits_carray.h @@ -0,0 +1,76 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_CARRAY_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_CARRAY_H_ + +#include "mojo/public/cpp/bindings/array_traits.h" + +namespace mojo { + +template +struct CArray { + CArray() : size(0), max_size(0), data(nullptr) {} + CArray(size_t size, size_t max_size, T* data) + : size(size), max_size(max_size), data(data) {} + size_t size; + const size_t max_size; + T* data; +}; + +template +struct ConstCArray { + ConstCArray() : size(0), data(nullptr) {} + ConstCArray(size_t size, const T* data) : size(size), data(data) {} + size_t size; + const T* data; +}; + +template +struct ArrayTraits> { + using Element = T; + + static bool IsNull(const CArray& input) { return !input.data; } + + static void SetToNull(CArray* output) { output->data = nullptr; } + + static size_t GetSize(const CArray& input) { return input.size; } + + static T* GetData(CArray& input) { return input.data; } + + static const T* GetData(const CArray& input) { return input.data; } + + static T& GetAt(CArray& input, size_t index) { return input.data[index]; } + + static const T& GetAt(const CArray& input, size_t index) { + return input.data[index]; + } + + static bool Resize(CArray& input, size_t size) { + if (size > input.max_size) + return false; + + input.size = size; + return true; + } +}; + +template +struct ArrayTraits> { + using Element = T; + + static bool IsNull(const ConstCArray& input) { return !input.data; } + + static size_t GetSize(const ConstCArray& input) { return input.size; } + + static const T* GetData(const ConstCArray& input) { return input.data; } + + static const T& GetAt(const ConstCArray& input, size_t index) { + return input.data[index]; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_CARRAY_H_ diff --git a/mojo/public/cpp/bindings/array_traits_stl.h b/mojo/public/cpp/bindings/array_traits_stl.h new file mode 100644 index 0000000000000000000000000000000000000000..dec47bfe7df9343cfc83778822b3c28ef50c59e8 --- /dev/null +++ b/mojo/public/cpp/bindings/array_traits_stl.h @@ -0,0 +1,127 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_STL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_STL_H_ + +#include +#include +#include + +#include "mojo/public/cpp/bindings/array_traits.h" + +namespace mojo { + +template +struct ArrayTraits> { + using Element = T; + + static bool IsNull(const std::vector& input) { + // std::vector<> is always converted to non-null mojom array. + return false; + } + + static void SetToNull(std::vector* output) { + // std::vector<> doesn't support null state. Set it to empty instead. + output->clear(); + } + + static size_t GetSize(const std::vector& input) { return input.size(); } + + static T* GetData(std::vector& input) { return input.data(); } + + static const T* GetData(const std::vector& input) { return input.data(); } + + static typename std::vector::reference GetAt(std::vector& input, + size_t index) { + return input[index]; + } + + static typename std::vector::const_reference GetAt( + const std::vector& input, + size_t index) { + return input[index]; + } + + static inline bool Resize(std::vector& input, size_t size) { + // Instead of calling std::vector::resize() directly, this is a hack to + // make compilers happy. Some compilers (e.g., Mac, Android, Linux MSan) + // currently don't allow resizing types like + // std::vector>. + // Because the deserialization code doesn't care about the original contents + // of |input|, we discard them directly. + // + // The "inline" keyword of this method matters. Without it, we have observed + // significant perf regression with some tests on Mac. crbug.com/631415 + if (input.size() != size) { + std::vector temp(size); + input.swap(temp); + } + + return true; + } +}; + +// This ArrayTraits specialization is used only for serialization. +template +struct ArrayTraits> { + using Element = T; + using ConstIterator = typename std::set::const_iterator; + + static bool IsNull(const std::set& input) { + // std::set<> is always converted to non-null mojom array. + return false; + } + + static size_t GetSize(const std::set& input) { return input.size(); } + + static ConstIterator GetBegin(const std::set& input) { + return input.begin(); + } + static void AdvanceIterator(ConstIterator& iterator) { + ++iterator; + } + static const T& GetValue(ConstIterator& iterator) { + return *iterator; + } +}; + +template +struct MapValuesArrayView { + explicit MapValuesArrayView(const std::map& map) : map(map) {} + const std::map& map; +}; + +// Convenience function to create a MapValuesArrayView<> that infers the +// template arguments from its argument type. +template +MapValuesArrayView MapValuesToArray(const std::map& map) { + return MapValuesArrayView(map); +} + +// This ArrayTraits specialization is used only for serialization and converts +// a map into an array, discarding the keys. +template +struct ArrayTraits> { + using Element = V; + using ConstIterator = typename std::map::const_iterator; + + static bool IsNull(const MapValuesArrayView& input) { + // std::map<> is always converted to non-null mojom array. + return false; + } + + static size_t GetSize(const MapValuesArrayView& input) { + return input.map.size(); + } + static ConstIterator GetBegin(const MapValuesArrayView& input) { + return input.map.begin(); + } + static void AdvanceIterator(ConstIterator& iterator) { ++iterator; } + static const V& GetValue(ConstIterator& iterator) { return iterator->second; } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_STL_H_ diff --git a/mojo/public/cpp/bindings/array_traits_wtf_vector.h b/mojo/public/cpp/bindings/array_traits_wtf_vector.h new file mode 100644 index 0000000000000000000000000000000000000000..6e207351fd070e157a08de6de53d5c130647740c --- /dev/null +++ b/mojo/public/cpp/bindings/array_traits_wtf_vector.h @@ -0,0 +1,47 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_WTF_VECTOR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_WTF_VECTOR_H_ + +#include "mojo/public/cpp/bindings/array_traits.h" +#include "third_party/WebKit/Source/wtf/Vector.h" + +namespace mojo { + +template +struct ArrayTraits> { + using Element = U; + + static bool IsNull(const WTF::Vector& input) { + // WTF::Vector<> is always converted to non-null mojom array. + return false; + } + + static void SetToNull(WTF::Vector* output) { + // WTF::Vector<> doesn't support null state. Set it to empty instead. + output->clear(); + } + + static size_t GetSize(const WTF::Vector& input) { return input.size(); } + + static U* GetData(WTF::Vector& input) { return input.data(); } + + static const U* GetData(const WTF::Vector& input) { return input.data(); } + + static U& GetAt(WTF::Vector& input, size_t index) { return input[index]; } + + static const U& GetAt(const WTF::Vector& input, size_t index) { + return input[index]; + } + + static bool Resize(WTF::Vector& input, size_t size) { + input.resize(size); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_WTF_VECTOR_H_ diff --git a/mojo/public/cpp/bindings/associated_binding.h b/mojo/public/cpp/bindings/associated_binding.h new file mode 100644 index 0000000000000000000000000000000000000000..59411666f5aca80a6e05953070ccb821ef2040e3 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_binding.h @@ -0,0 +1,171 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_H_ + +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +class MessageReceiver; + +// Base class used to factor out code in AssociatedBinding expansions, in +// particular for Bind(). +class MOJO_CPP_BINDINGS_EXPORT AssociatedBindingBase { + public: + AssociatedBindingBase(); + ~AssociatedBindingBase(); + + // Adds a message filter to be notified of each incoming message before + // dispatch. If a filter returns |false| from Accept(), the message is not + // dispatched and the pipe is closed. Filters cannot be removed. + void AddFilter(std::unique_ptr filter); + + // Closes the associated interface. Puts this object into a state where it can + // be rebound. + void Close(); + + // Similar to the method above, but also specifies a disconnect reason. + void CloseWithReason(uint32_t custom_reason, const std::string& description); + + // Sets an error handler that will be called if a connection error occurs. + // + // This method may only be called after this AssociatedBinding has been bound + // to a message pipe. The error handler will be reset when this + // AssociatedBinding is unbound or closed. + void set_connection_error_handler(const base::Closure& error_handler); + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler); + + // Indicates whether the associated binding has been completed. + bool is_bound() const { return !!endpoint_client_; } + + // Sends a message on the underlying message pipe and runs the current + // message loop until its response is received. This can be used in tests to + // verify that no message was sent on a message pipe in response to some + // stimulus. + void FlushForTesting(); + + protected: + void BindImpl(ScopedInterfaceEndpointHandle handle, + MessageReceiverWithResponderStatus* receiver, + std::unique_ptr payload_validator, + bool expect_sync_requests, + scoped_refptr runner, + uint32_t interface_version); + + std::unique_ptr endpoint_client_; +}; + +// Represents the implementation side of an associated interface. It is similar +// to Binding, except that it doesn't own a message pipe handle. +// +// When you bind this class to a request, optionally you can specify a +// base::SingleThreadTaskRunner. This task runner must belong to the same +// thread. It will be used to dispatch incoming method calls and connection +// error notification. It is useful when you attach multiple task runners to a +// single thread for the purposes of task scheduling. Please note that incoming +// synchrounous method calls may not be run from this task runner, when they +// reenter outgoing synchrounous calls on the same thread. +template > +class AssociatedBinding : public AssociatedBindingBase { + public: + using ImplPointerType = typename ImplRefTraits::PointerType; + + // Constructs an incomplete associated binding that will use the + // implementation |impl|. It may be completed with a subsequent call to the + // |Bind| method. Does not take ownership of |impl|, which must outlive this + // object. + explicit AssociatedBinding(ImplPointerType impl) { stub_.set_sink(impl); } + + // Constructs a completed associated binding of |impl|. The output |ptr_info| + // should be sent by another interface. |impl| must outlive this object. + AssociatedBinding(ImplPointerType impl, + AssociatedInterfacePtrInfo* ptr_info, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) + : AssociatedBinding(std::move(impl)) { + Bind(ptr_info, std::move(runner)); + } + + // Constructs a completed associated binding of |impl|. |impl| must outlive + // the binding. + AssociatedBinding(ImplPointerType impl, + AssociatedInterfaceRequest request, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) + : AssociatedBinding(std::move(impl)) { + Bind(std::move(request), std::move(runner)); + } + + ~AssociatedBinding() {} + + // Creates an associated inteface and sets up this object as the + // implementation side. The output |ptr_info| should be sent by another + // interface. + void Bind(AssociatedInterfacePtrInfo* ptr_info, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + auto request = MakeRequest(ptr_info); + ptr_info->set_version(Interface::Version_); + Bind(std::move(request), std::move(runner)); + } + + // Sets up this object as the implementation side of an associated interface. + void Bind(AssociatedInterfaceRequest request, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + BindImpl(request.PassHandle(), &stub_, + base::WrapUnique(new typename Interface::RequestValidator_()), + Interface::HasSyncMethods_, std::move(runner), + Interface::Version_); + } + + // Unbinds and returns the associated interface request so it can be + // used in another context, such as on another thread or with a different + // implementation. Puts this object into a state where it can be rebound. + AssociatedInterfaceRequest Unbind() { + DCHECK(endpoint_client_); + + AssociatedInterfaceRequest request; + request.Bind(endpoint_client_->PassHandle()); + + endpoint_client_.reset(); + + return request; + } + + // Returns the interface implementation that was previously specified. + Interface* impl() { return ImplRefTraits::GetRawPointer(&stub_.sink()); } + + private: + typename Interface::template Stub_ stub_; + + DISALLOW_COPY_AND_ASSIGN(AssociatedBinding); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_H_ diff --git a/mojo/public/cpp/bindings/associated_binding_set.h b/mojo/public/cpp/bindings/associated_binding_set.h new file mode 100644 index 0000000000000000000000000000000000000000..59600c64f3c9afaf534eb51f08fa9a8bdf23c486 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_binding_set.h @@ -0,0 +1,29 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_SET_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_SET_H_ + +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/binding_set.h" + +namespace mojo { + +template +struct BindingSetTraits> { + using ProxyType = AssociatedInterfacePtr; + using RequestType = AssociatedInterfaceRequest; + using BindingType = AssociatedBinding; + using ImplPointerType = typename BindingType::ImplPointerType; +}; + +template +using AssociatedBindingSet = + BindingSetBase, ContextType>; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_SET_H_ diff --git a/mojo/public/cpp/bindings/associated_group.h b/mojo/public/cpp/bindings/associated_group.h new file mode 100644 index 0000000000000000000000000000000000000000..14e78ec3f911c3280721bbab415d16bb6de2c469 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_group.h @@ -0,0 +1,51 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +class AssociatedGroupController; + +// AssociatedGroup refers to all the interface endpoints running at one end of a +// message pipe. +// It is thread safe and cheap to make copies. +class MOJO_CPP_BINDINGS_EXPORT AssociatedGroup { + public: + AssociatedGroup(); + + explicit AssociatedGroup(scoped_refptr controller); + + explicit AssociatedGroup(const ScopedInterfaceEndpointHandle& handle); + + AssociatedGroup(const AssociatedGroup& other); + + ~AssociatedGroup(); + + AssociatedGroup& operator=(const AssociatedGroup& other); + + // The return value of this getter if this object is initialized with a + // ScopedInterfaceEndpointHandle: + // - If the handle is invalid, the return value will always be null. + // - If the handle is valid and non-pending, the return value will be + // non-null and remain unchanged even if the handle is later reset. + // - If the handle is pending asssociation, the return value will initially + // be null, change to non-null when/if the handle is associated, and + // remain unchanged ever since. + AssociatedGroupController* GetController(); + + private: + base::Callback controller_getter_; + scoped_refptr controller_; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_H_ diff --git a/mojo/public/cpp/bindings/associated_group_controller.h b/mojo/public/cpp/bindings/associated_group_controller.h new file mode 100644 index 0000000000000000000000000000000000000000..d33c2776d5de476bce2b9a18b2c86a79020306bc --- /dev/null +++ b/mojo/public/cpp/bindings/associated_group_controller.h @@ -0,0 +1,93 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_CONTROLLER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_CONTROLLER_H_ + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/disconnect_reason.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +class InterfaceEndpointClient; +class InterfaceEndpointController; + +// An internal interface used to manage endpoints within an associated group, +// which corresponds to one end of a message pipe. +class MOJO_CPP_BINDINGS_EXPORT AssociatedGroupController + : public base::RefCountedThreadSafe { + public: + // Associates an interface with this AssociatedGroupController's message pipe. + // It takes ownership of |handle_to_send| and returns an interface ID that + // could be sent by any endpoints within the same associated group. + // If |handle_to_send| is not in pending association state, it returns + // kInvalidInterfaceId. Otherwise, the peer handle of |handle_to_send| joins + // the associated group and is no longer pending. + virtual InterfaceId AssociateInterface( + ScopedInterfaceEndpointHandle handle_to_send) = 0; + + // Creates an interface endpoint handle from a given interface ID. The handle + // joins this associated group. + // Typically, this method is used to (1) create an endpoint handle for the + // master interface; or (2) create an endpoint handle on receiving an + // interface ID from the message pipe. + // + // On failure, the method returns an invalid handle. Usually that is because + // the ID has already been used to create a handle. + virtual ScopedInterfaceEndpointHandle CreateLocalEndpointHandle( + InterfaceId id) = 0; + + // Closes an interface endpoint handle. + virtual void CloseEndpointHandle( + InterfaceId id, + const base::Optional& reason) = 0; + + // Attaches a client to the specified endpoint to send and receive messages. + // The returned object is still owned by the controller. It must only be used + // on the same thread as this call, and only before the client is detached + // using DetachEndpointClient(). + virtual InterfaceEndpointController* AttachEndpointClient( + const ScopedInterfaceEndpointHandle& handle, + InterfaceEndpointClient* endpoint_client, + scoped_refptr runner) = 0; + + // Detaches the client attached to the specified endpoint. It must be called + // on the same thread as the corresponding AttachEndpointClient() call. + virtual void DetachEndpointClient( + const ScopedInterfaceEndpointHandle& handle) = 0; + + // Raises an error on the underlying message pipe. It disconnects the pipe + // and notifies all interfaces running on this pipe. + virtual void RaiseError() = 0; + + protected: + friend class base::RefCountedThreadSafe; + + // Creates a new ScopedInterfaceEndpointHandle within this associated group. + ScopedInterfaceEndpointHandle CreateScopedInterfaceEndpointHandle( + InterfaceId id); + + // Notifies that the interface represented by |handle_to_send| and its peer + // has been associated with this AssociatedGroupController's message pipe, and + // |handle_to_send|'s peer has joined this associated group. (Note: it is the + // peer who has joined the associated group; |handle_to_send| will be sent to + // the remote side.) + // Returns false if |handle_to_send|'s peer has closed. + bool NotifyAssociation(ScopedInterfaceEndpointHandle* handle_to_send, + InterfaceId id); + + virtual ~AssociatedGroupController(); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_CONTROLLER_H_ diff --git a/mojo/public/cpp/bindings/associated_interface_ptr.h b/mojo/public/cpp/bindings/associated_interface_ptr.h new file mode 100644 index 0000000000000000000000000000000000000000..8806a3e0901d23782c34435f483994c4dcfc2024 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_interface_ptr.h @@ -0,0 +1,283 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_H_ + +#include + +#include +#include + +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h" +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace mojo { + +// Represents the client side of an associated interface. It is similar to +// InterfacePtr, except that it doesn't own a message pipe handle. +template +class AssociatedInterfacePtr { + public: + using InterfaceType = Interface; + using PtrInfoType = AssociatedInterfacePtrInfo; + + // Constructs an unbound AssociatedInterfacePtr. + AssociatedInterfacePtr() {} + AssociatedInterfacePtr(decltype(nullptr)) {} + + AssociatedInterfacePtr(AssociatedInterfacePtr&& other) { + internal_state_.Swap(&other.internal_state_); + } + + AssociatedInterfacePtr& operator=(AssociatedInterfacePtr&& other) { + reset(); + internal_state_.Swap(&other.internal_state_); + return *this; + } + + // Assigning nullptr to this class causes it to closes the associated + // interface (if any) and returns the pointer to the unbound state. + AssociatedInterfacePtr& operator=(decltype(nullptr)) { + reset(); + return *this; + } + + ~AssociatedInterfacePtr() {} + + // Sets up this object as the client side of an associated interface. + // Calling with an invalid |info| has the same effect as reset(). In this + // case, the AssociatedInterfacePtr is not considered as bound. + // + // |runner| must belong to the same thread. It will be used to dispatch all + // callbacks and connection error notification. It is useful when you attach + // multiple task runners to a single thread for the purposes of task + // scheduling. + // + // NOTE: The corresponding AssociatedInterfaceRequest must be sent over + // another interface before using this object to make calls. Please see the + // comments of MakeRequest(AssociatedInterfacePtr*) for more + // details. + void Bind(AssociatedInterfacePtrInfo info, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + reset(); + + if (info.is_valid()) + internal_state_.Bind(std::move(info), std::move(runner)); + } + + bool is_bound() const { return internal_state_.is_bound(); } + + Interface* get() const { return internal_state_.instance(); } + + // Functions like a pointer to Interface. Must already be bound. + Interface* operator->() const { return get(); } + Interface& operator*() const { return *get(); } + + // Returns the version number of the interface that the remote side supports. + uint32_t version() const { return internal_state_.version(); } + + // Queries the max version that the remote side supports. On completion, the + // result will be returned as the input of |callback|. The version number of + // this object will also be updated. + void QueryVersion(const base::Callback& callback) { + internal_state_.QueryVersion(callback); + } + + // If the remote side doesn't support the specified version, it will close the + // associated interface asynchronously. This does nothing if it's already + // known that the remote side supports the specified version, i.e., if + // |version <= this->version()|. + // + // After calling RequireVersion() with a version not supported by the remote + // side, all subsequent calls to interface methods will be ignored. + void RequireVersion(uint32_t version) { + internal_state_.RequireVersion(version); + } + + // Sends a message on the underlying message pipe and runs the current + // message loop until its response is received. This can be used in tests to + // verify that no message was sent on a message pipe in response to some + // stimulus. + void FlushForTesting() { internal_state_.FlushForTesting(); } + + // Closes the associated interface (if any) and returns the pointer to the + // unbound state. + void reset() { + State doomed; + internal_state_.Swap(&doomed); + } + + // Similar to the method above, but also specifies a disconnect reason. + void ResetWithReason(uint32_t custom_reason, const std::string& description) { + if (internal_state_.is_bound()) + internal_state_.CloseWithReason(custom_reason, description); + reset(); + } + + // Indicates whether an error has been encountered. If true, method calls made + // on this interface will be dropped (and may already have been dropped). + bool encountered_error() const { return internal_state_.encountered_error(); } + + // Registers a handler to receive error notifications. + // + // This method may only be called after the AssociatedInterfacePtr has been + // bound. + void set_connection_error_handler(const base::Closure& error_handler) { + internal_state_.set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + internal_state_.set_connection_error_with_reason_handler(error_handler); + } + + // Unbinds and returns the associated interface pointer information which + // could be used to setup an AssociatedInterfacePtr again. This method may be + // used to move the proxy to a different thread. + // + // It is an error to call PassInterface() while there are pending responses. + // TODO: fix this restriction, it's not always obvious when there is a + // pending response. + AssociatedInterfacePtrInfo PassInterface() { + DCHECK(!internal_state_.has_pending_callbacks()); + State state; + internal_state_.Swap(&state); + + return state.PassInterface(); + } + + // DO NOT USE. Exposed only for internal use and for testing. + internal::AssociatedInterfacePtrState* internal_state() { + return &internal_state_; + } + + // Allow AssociatedInterfacePtr<> to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + private: + // TODO(dcheng): Use an explicit conversion operator. + typedef internal::AssociatedInterfacePtrState + AssociatedInterfacePtr::*Testable; + + public: + operator Testable() const { + return internal_state_.is_bound() ? &AssociatedInterfacePtr::internal_state_ + : nullptr; + } + + private: + // Forbid the == and != operators explicitly, otherwise AssociatedInterfacePtr + // will be converted to Testable to do == or != comparison. + template + bool operator==(const AssociatedInterfacePtr& other) const = delete; + template + bool operator!=(const AssociatedInterfacePtr& other) const = delete; + + typedef internal::AssociatedInterfacePtrState State; + mutable State internal_state_; + + DISALLOW_COPY_AND_ASSIGN(AssociatedInterfacePtr); +}; + +// Creates an associated interface. The returned request is supposed to be sent +// over another interface (either associated or non-associated). +// +// NOTE: |ptr| must NOT be used to make calls before the request is sent. +// Violating that will lead to crash. On the other hand, as soon as the request +// is sent, |ptr| is usable. There is no need to wait until the request is bound +// to an implementation at the remote side. +template +AssociatedInterfaceRequest MakeRequest( + AssociatedInterfacePtr* ptr, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + AssociatedInterfacePtrInfo ptr_info; + auto request = MakeRequest(&ptr_info); + ptr->Bind(std::move(ptr_info), std::move(runner)); + return request; +} + +// Creates an associated interface. One of the two endpoints is supposed to be +// sent over another interface (either associated or non-associated); while the +// other is used locally. +// +// NOTE: If |ptr_info| is used locally and bound to an AssociatedInterfacePtr, +// the interface pointer must NOT be used to make calls before the request is +// sent. Please see NOTE of the previous function for more details. +template +AssociatedInterfaceRequest MakeRequest( + AssociatedInterfacePtrInfo* ptr_info) { + ScopedInterfaceEndpointHandle handle0; + ScopedInterfaceEndpointHandle handle1; + ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&handle0, + &handle1); + + ptr_info->set_handle(std::move(handle0)); + ptr_info->set_version(0); + + AssociatedInterfaceRequest request; + request.Bind(std::move(handle1)); + return request; +} + +// Like MakeRequest() above, but it creates a dedicated message pipe. The +// returned request can be bound directly to an implementation, without being +// first passed through a message pipe endpoint. +// +// This function has two main uses: +// +// * In testing, where the returned request is bound to e.g. a mock and there +// are no other interfaces involved. +// +// * When discarding messages sent on an interface, which can be done by +// discarding the returned request. +template +AssociatedInterfaceRequest MakeIsolatedRequest( + AssociatedInterfacePtr* ptr) { + MessagePipe pipe; + scoped_refptr router0 = + new internal::MultiplexRouter(std::move(pipe.handle0), + internal::MultiplexRouter::MULTI_INTERFACE, + false, base::ThreadTaskRunnerHandle::Get()); + scoped_refptr router1 = + new internal::MultiplexRouter(std::move(pipe.handle1), + internal::MultiplexRouter::MULTI_INTERFACE, + true, base::ThreadTaskRunnerHandle::Get()); + + ScopedInterfaceEndpointHandle endpoint0, endpoint1; + ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&endpoint0, + &endpoint1); + InterfaceId id = router1->AssociateInterface(std::move(endpoint0)); + endpoint0 = router0->CreateLocalEndpointHandle(id); + + ptr->Bind(AssociatedInterfacePtrInfo(std::move(endpoint0), + Interface::Version_)); + + AssociatedInterfaceRequest request; + request.Bind(std::move(endpoint1)); + return request; +} + +// |handle| is supposed to be the request of an associated interface. This +// method associates the interface with a dedicated, disconnected message pipe. +// That way, the corresponding associated interface pointer of |handle| can +// safely make calls (although those calls are silently dropped). +MOJO_CPP_BINDINGS_EXPORT void GetIsolatedInterface( + ScopedInterfaceEndpointHandle handle); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_H_ diff --git a/mojo/public/cpp/bindings/associated_interface_ptr_info.h b/mojo/public/cpp/bindings/associated_interface_ptr_info.h new file mode 100644 index 0000000000000000000000000000000000000000..3c6ca54603177f09bf913ac7a18a65e6b5dc64ac --- /dev/null +++ b/mojo/public/cpp/bindings/associated_interface_ptr_info.h @@ -0,0 +1,77 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_ + +#include +#include + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +// AssociatedInterfacePtrInfo stores necessary information to construct an +// associated interface pointer. It is similar to InterfacePtrInfo except that +// it doesn't own a message pipe handle. +template +class AssociatedInterfacePtrInfo { + public: + AssociatedInterfacePtrInfo() : version_(0u) {} + AssociatedInterfacePtrInfo(std::nullptr_t) : version_(0u) {} + + AssociatedInterfacePtrInfo(AssociatedInterfacePtrInfo&& other) + : handle_(std::move(other.handle_)), version_(other.version_) { + other.version_ = 0u; + } + + AssociatedInterfacePtrInfo(ScopedInterfaceEndpointHandle handle, + uint32_t version) + : handle_(std::move(handle)), version_(version) {} + + ~AssociatedInterfacePtrInfo() {} + + AssociatedInterfacePtrInfo& operator=(AssociatedInterfacePtrInfo&& other) { + if (this != &other) { + handle_ = std::move(other.handle_); + version_ = other.version_; + other.version_ = 0u; + } + + return *this; + } + + bool is_valid() const { return handle_.is_valid(); } + + ScopedInterfaceEndpointHandle PassHandle() { + return std::move(handle_); + } + const ScopedInterfaceEndpointHandle& handle() const { return handle_; } + void set_handle(ScopedInterfaceEndpointHandle handle) { + handle_ = std::move(handle); + } + + uint32_t version() const { return version_; } + void set_version(uint32_t version) { version_ = version; } + + bool Equals(const AssociatedInterfacePtrInfo& other) const { + if (this == &other) + return true; + + // Now that the two refer to different objects, they are equivalent if + // and only if they are both invalid. + return !is_valid() && !other.is_valid(); + } + + private: + ScopedInterfaceEndpointHandle handle_; + uint32_t version_; + + DISALLOW_COPY_AND_ASSIGN(AssociatedInterfacePtrInfo); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_ diff --git a/mojo/public/cpp/bindings/associated_interface_request.h b/mojo/public/cpp/bindings/associated_interface_request.h new file mode 100644 index 0000000000000000000000000000000000000000..c37636c9f31c030779de3ff1cf41aa753aef8674 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_interface_request.h @@ -0,0 +1,90 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_ + +#include +#include + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +// AssociatedInterfaceRequest represents an associated interface request. It is +// similar to InterfaceRequest except that it doesn't own a message pipe handle. +template +class AssociatedInterfaceRequest { + public: + // Constructs an empty AssociatedInterfaceRequest, representing that the + // client is not requesting an implementation of Interface. + AssociatedInterfaceRequest() {} + AssociatedInterfaceRequest(decltype(nullptr)) {} + + // Takes the interface endpoint handle from another + // AssociatedInterfaceRequest. + AssociatedInterfaceRequest(AssociatedInterfaceRequest&& other) { + handle_ = std::move(other.handle_); + } + AssociatedInterfaceRequest& operator=(AssociatedInterfaceRequest&& other) { + if (this != &other) + handle_ = std::move(other.handle_); + return *this; + } + + // Assigning to nullptr resets the AssociatedInterfaceRequest to an empty + // state, closing the interface endpoint handle currently bound to it (if + // any). + AssociatedInterfaceRequest& operator=(decltype(nullptr)) { + handle_.reset(); + return *this; + } + + // Indicates whether the request currently contains a valid interface endpoint + // handle. + bool is_pending() const { return handle_.is_valid(); } + + void Bind(ScopedInterfaceEndpointHandle handle) { + handle_ = std::move(handle); + } + + ScopedInterfaceEndpointHandle PassHandle() { + return std::move(handle_); + } + + const ScopedInterfaceEndpointHandle& handle() const { return handle_; } + + bool Equals(const AssociatedInterfaceRequest& other) const { + if (this == &other) + return true; + + // Now that the two refer to different objects, they are equivalent if + // and only if they are both invalid. + return !is_pending() && !other.is_pending(); + } + + void ResetWithReason(uint32_t custom_reason, const std::string& description) { + handle_.ResetWithReason(custom_reason, description); + } + + private: + ScopedInterfaceEndpointHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(AssociatedInterfaceRequest); +}; + +// Makes an AssociatedInterfaceRequest bound to the specified associated +// endpoint. +template +AssociatedInterfaceRequest MakeAssociatedRequest( + ScopedInterfaceEndpointHandle handle) { + AssociatedInterfaceRequest request; + request.Bind(std::move(handle)); + return request; +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_ diff --git a/mojo/public/cpp/bindings/binding.h b/mojo/public/cpp/bindings/binding.h new file mode 100644 index 0000000000000000000000000000000000000000..88d2f4ba3eb92cfe9df568d5bb3185a175dcc3dc --- /dev/null +++ b/mojo/public/cpp/bindings/binding.h @@ -0,0 +1,276 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_ + +#include +#include + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/lib/binding_state.h" +#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +class MessageReceiver; + +// Represents the binding of an interface implementation to a message pipe. +// When the |Binding| object is destroyed, the binding between the message pipe +// and the interface is torn down and the message pipe is closed, leaving the +// interface implementation in an unbound state. +// +// Example: +// +// #include "foo.mojom.h" +// +// class FooImpl : public Foo { +// public: +// explicit FooImpl(InterfaceRequest request) +// : binding_(this, std::move(request)) {} +// +// // Foo implementation here. +// +// private: +// Binding binding_; +// }; +// +// class MyFooFactory : public InterfaceFactory { +// public: +// void Create(..., InterfaceRequest request) override { +// auto f = new FooImpl(std::move(request)); +// // Do something to manage the lifetime of |f|. Use StrongBinding<> to +// // delete FooImpl on connection errors. +// } +// }; +// +// This class is thread hostile while bound to a message pipe. All calls to this +// class must be from the thread that bound it. The interface implementation's +// methods will be called from the thread that bound this. If a Binding is not +// bound to a message pipe, it may be bound or destroyed on any thread. +// +// When you bind this class to a message pipe, optionally you can specify a +// base::SingleThreadTaskRunner. This task runner must belong to the same +// thread. It will be used to dispatch incoming method calls and connection +// error notification. It is useful when you attach multiple task runners to a +// single thread for the purposes of task scheduling. Please note that incoming +// synchrounous method calls may not be run from this task runner, when they +// reenter outgoing synchrounous calls on the same thread. +template > +class Binding { + public: + using ImplPointerType = typename ImplRefTraits::PointerType; + + // Constructs an incomplete binding that will use the implementation |impl|. + // The binding may be completed with a subsequent call to the |Bind| method. + // Does not take ownership of |impl|, which must outlive the binding. + explicit Binding(ImplPointerType impl) : internal_state_(std::move(impl)) {} + + // Constructs a completed binding of message pipe |handle| to implementation + // |impl|. Does not take ownership of |impl|, which must outlive the binding. + Binding(ImplPointerType impl, + ScopedMessagePipeHandle handle, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) + : Binding(std::move(impl)) { + Bind(std::move(handle), std::move(runner)); + } + + // Constructs a completed binding of |impl| to a new message pipe, passing the + // client end to |ptr|, which takes ownership of it. The caller is expected to + // pass |ptr| on to the client of the service. Does not take ownership of any + // of the parameters. |impl| must outlive the binding. |ptr| only needs to + // last until the constructor returns. + Binding(ImplPointerType impl, + InterfacePtr* ptr, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) + : Binding(std::move(impl)) { + Bind(ptr, std::move(runner)); + } + + // Constructs a completed binding of |impl| to the message pipe endpoint in + // |request|, taking ownership of the endpoint. Does not take ownership of + // |impl|, which must outlive the binding. + Binding(ImplPointerType impl, + InterfaceRequest request, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) + : Binding(std::move(impl)) { + Bind(request.PassMessagePipe(), std::move(runner)); + } + + // Tears down the binding, closing the message pipe and leaving the interface + // implementation unbound. + ~Binding() {} + + // Returns an InterfacePtr bound to one end of a pipe whose other end is + // bound to |this|. + InterfacePtr CreateInterfacePtrAndBind( + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + InterfacePtr interface_ptr; + Bind(&interface_ptr, std::move(runner)); + return interface_ptr; + } + + // Completes a binding that was constructed with only an interface + // implementation. Takes ownership of |handle| and binds it to the previously + // specified implementation. + void Bind(ScopedMessagePipeHandle handle, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + internal_state_.Bind(std::move(handle), std::move(runner)); + } + + // Completes a binding that was constructed with only an interface + // implementation by creating a new message pipe, binding one end of it to the + // previously specified implementation, and passing the other to |ptr|, which + // takes ownership of it. The caller is expected to pass |ptr| on to the + // eventual client of the service. Does not take ownership of |ptr|. + void Bind(InterfacePtr* ptr, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + MessagePipe pipe; + ptr->Bind(InterfacePtrInfo(std::move(pipe.handle0), + Interface::Version_), + runner); + Bind(std::move(pipe.handle1), std::move(runner)); + } + + // Completes a binding that was constructed with only an interface + // implementation by removing the message pipe endpoint from |request| and + // binding it to the previously specified implementation. + void Bind(InterfaceRequest request, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + Bind(request.PassMessagePipe(), std::move(runner)); + } + + // Adds a message filter to be notified of each incoming message before + // dispatch. If a filter returns |false| from Accept(), the message is not + // dispatched and the pipe is closed. Filters cannot be removed. + void AddFilter(std::unique_ptr filter) { + DCHECK(is_bound()); + internal_state_.AddFilter(std::move(filter)); + } + + // Whether there are any associated interfaces running on the pipe currently. + bool HasAssociatedInterfaces() const { + return internal_state_.HasAssociatedInterfaces(); + } + + // Stops processing incoming messages until + // ResumeIncomingMethodCallProcessing(), or WaitForIncomingMethodCall(). + // Outgoing messages are still sent. + // + // No errors are detected on the message pipe while paused. + // + // This method may only be called if the object has been bound to a message + // pipe and there are no associated interfaces running. + void PauseIncomingMethodCallProcessing() { + CHECK(!HasAssociatedInterfaces()); + internal_state_.PauseIncomingMethodCallProcessing(); + } + void ResumeIncomingMethodCallProcessing() { + internal_state_.ResumeIncomingMethodCallProcessing(); + } + + // Blocks the calling thread until either a call arrives on the previously + // bound message pipe, the deadline is exceeded, or an error occurs. Returns + // true if a method was successfully read and dispatched. + // + // This method may only be called if the object has been bound to a message + // pipe. This returns once a message is received either on the master + // interface or any associated interfaces. + bool WaitForIncomingMethodCall( + MojoDeadline deadline = MOJO_DEADLINE_INDEFINITE) { + return internal_state_.WaitForIncomingMethodCall(deadline); + } + + // Closes the message pipe that was previously bound. Put this object into a + // state where it can be rebound to a new pipe. + void Close() { internal_state_.Close(); } + + // Similar to the method above, but also specifies a disconnect reason. + void CloseWithReason(uint32_t custom_reason, const std::string& description) { + internal_state_.CloseWithReason(custom_reason, description); + } + + // Unbinds the underlying pipe from this binding and returns it so it can be + // used in another context, such as on another thread or with a different + // implementation. Put this object into a state where it can be rebound to a + // new pipe. + // + // This method may only be called if the object has been bound to a message + // pipe and there are no associated interfaces running. + // + // TODO(yzshen): For now, users need to make sure there is no one holding + // on to associated interface endpoint handles at both sides of the + // message pipe in order to call this method. We need a way to forcefully + // invalidate associated interface endpoint handles. + InterfaceRequest Unbind() { + CHECK(!HasAssociatedInterfaces()); + return internal_state_.Unbind(); + } + + // Sets an error handler that will be called if a connection error occurs on + // the bound message pipe. + // + // This method may only be called after this Binding has been bound to a + // message pipe. The error handler will be reset when this Binding is unbound + // or closed. + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(is_bound()); + internal_state_.set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(is_bound()); + internal_state_.set_connection_error_with_reason_handler(error_handler); + } + + // Returns the interface implementation that was previously specified. Caller + // does not take ownership. + Interface* impl() { return internal_state_.impl(); } + + // Indicates whether the binding has been completed (i.e., whether a message + // pipe has been bound to the implementation). + bool is_bound() const { return internal_state_.is_bound(); } + + // Returns the value of the handle currently bound to this Binding which can + // be used to make explicit Wait/WaitMany calls. Requires that the Binding be + // bound. Ownership of the handle is retained by the Binding, it is not + // transferred to the caller. + MessagePipeHandle handle() const { return internal_state_.handle(); } + + // Sends a no-op message on the underlying message pipe and runs the current + // message loop until its response is received. This can be used in tests to + // verify that no message was sent on a message pipe in response to some + // stimulus. + void FlushForTesting() { internal_state_.FlushForTesting(); } + + // Exposed for testing, should not generally be used. + void EnableTestingMode() { internal_state_.EnableTestingMode(); } + + private: + internal::BindingState internal_state_; + + DISALLOW_COPY_AND_ASSIGN(Binding); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_ diff --git a/mojo/public/cpp/bindings/binding_set.h b/mojo/public/cpp/bindings/binding_set.h new file mode 100644 index 0000000000000000000000000000000000000000..919f9c09adbffaf13287f1735fd24cb0e265ec13 --- /dev/null +++ b/mojo/public/cpp/bindings/binding_set.h @@ -0,0 +1,267 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_ + +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { + +template +struct BindingSetTraits; + +template +struct BindingSetTraits> { + using ProxyType = InterfacePtr; + using RequestType = InterfaceRequest; + using BindingType = Binding; + using ImplPointerType = typename BindingType::ImplPointerType; + + static RequestType MakeRequest(ProxyType* proxy) { + return mojo::MakeRequest(proxy); + } +}; + +using BindingId = size_t; + +template +struct BindingSetContextTraits { + using Type = ContextType; + + static constexpr bool SupportsContext() { return true; } +}; + +template <> +struct BindingSetContextTraits { + // NOTE: This choice of Type only matters insofar as it affects the size of + // the |context_| field of a BindingSetBase::Entry with void context. The + // context value is never used in this case. + using Type = bool; + + static constexpr bool SupportsContext() { return false; } +}; + +// Generic definition used for BindingSet and AssociatedBindingSet to own a +// collection of bindings which point to the same implementation. +// +// If |ContextType| is non-void, then every added binding must include a context +// value of that type, and |dispatch_context()| will return that value during +// the extent of any message dispatch targeting that specific binding. +template +class BindingSetBase { + public: + using ContextTraits = BindingSetContextTraits; + using Context = typename ContextTraits::Type; + using PreDispatchCallback = base::Callback; + using Traits = BindingSetTraits; + using ProxyType = typename Traits::ProxyType; + using RequestType = typename Traits::RequestType; + using ImplPointerType = typename Traits::ImplPointerType; + + BindingSetBase() {} + + void set_connection_error_handler(const base::Closure& error_handler) { + error_handler_ = error_handler; + error_with_reason_handler_.Reset(); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + error_with_reason_handler_ = error_handler; + error_handler_.Reset(); + } + + // Sets a callback to be invoked immediately before dispatching any message or + // error received by any of the bindings in the set. This may only be used + // with a non-void |ContextType|. + void set_pre_dispatch_handler(const PreDispatchCallback& handler) { + static_assert(ContextTraits::SupportsContext(), + "Pre-dispatch handler usage requires non-void context type."); + pre_dispatch_handler_ = handler; + } + + // Adds a new binding to the set which binds |request| to |impl| with no + // additional context. + BindingId AddBinding(ImplPointerType impl, RequestType request) { + static_assert(!ContextTraits::SupportsContext(), + "Context value required for non-void context type."); + return AddBindingImpl(std::move(impl), std::move(request), false); + } + + // Adds a new binding associated with |context|. + BindingId AddBinding(ImplPointerType impl, + RequestType request, + Context context) { + static_assert(ContextTraits::SupportsContext(), + "Context value unsupported for void context type."); + return AddBindingImpl(std::move(impl), std::move(request), + std::move(context)); + } + + // Removes a binding from the set. Note that this is safe to call even if the + // binding corresponding to |id| has already been removed. + // + // Returns |true| if the binding was removed and |false| if it didn't exist. + bool RemoveBinding(BindingId id) { + auto it = bindings_.find(id); + if (it == bindings_.end()) + return false; + bindings_.erase(it); + return true; + } + + // Returns a proxy bound to one end of a pipe whose other end is bound to + // |this|. If |id_storage| is not null, |*id_storage| will be set to the ID + // of the added binding. + ProxyType CreateInterfacePtrAndBind(ImplPointerType impl, + BindingId* id_storage = nullptr) { + ProxyType proxy; + BindingId id = AddBinding(std::move(impl), Traits::MakeRequest(&proxy)); + if (id_storage) + *id_storage = id; + return proxy; + } + + void CloseAllBindings() { bindings_.clear(); } + + bool empty() const { return bindings_.empty(); } + + // Implementations may call this when processing a dispatched message or + // error. During the extent of message or error dispatch, this will return the + // context associated with the specific binding which received the message or + // error. Use AddBinding() to associated a context with a specific binding. + const Context& dispatch_context() const { + static_assert(ContextTraits::SupportsContext(), + "dispatch_context() requires non-void context type."); + DCHECK(dispatch_context_); + return *dispatch_context_; + } + + void FlushForTesting() { + for (auto& binding : bindings_) + binding.second->FlushForTesting(); + } + + private: + friend class Entry; + + class Entry { + public: + Entry(ImplPointerType impl, + RequestType request, + BindingSetBase* binding_set, + BindingId binding_id, + Context context) + : binding_(std::move(impl), std::move(request)), + binding_set_(binding_set), + binding_id_(binding_id), + context_(std::move(context)) { + if (ContextTraits::SupportsContext()) + binding_.AddFilter(base::MakeUnique(this)); + binding_.set_connection_error_with_reason_handler( + base::Bind(&Entry::OnConnectionError, base::Unretained(this))); + } + + void FlushForTesting() { binding_.FlushForTesting(); } + + private: + class DispatchFilter : public MessageReceiver { + public: + explicit DispatchFilter(Entry* entry) : entry_(entry) {} + ~DispatchFilter() override {} + + private: + // MessageReceiver: + bool Accept(Message* message) override { + entry_->WillDispatch(); + return true; + } + + Entry* entry_; + + DISALLOW_COPY_AND_ASSIGN(DispatchFilter); + }; + + void WillDispatch() { + DCHECK(ContextTraits::SupportsContext()); + binding_set_->SetDispatchContext(&context_); + } + + void OnConnectionError(uint32_t custom_reason, + const std::string& description) { + if (ContextTraits::SupportsContext()) + WillDispatch(); + binding_set_->OnConnectionError(binding_id_, custom_reason, description); + } + + BindingType binding_; + BindingSetBase* const binding_set_; + const BindingId binding_id_; + Context const context_; + + DISALLOW_COPY_AND_ASSIGN(Entry); + }; + + void SetDispatchContext(const Context* context) { + DCHECK(ContextTraits::SupportsContext()); + dispatch_context_ = context; + if (!pre_dispatch_handler_.is_null()) + pre_dispatch_handler_.Run(*context); + } + + BindingId AddBindingImpl(ImplPointerType impl, + RequestType request, + Context context) { + BindingId id = next_binding_id_++; + DCHECK_GE(next_binding_id_, 0u); + auto entry = base::MakeUnique(std::move(impl), std::move(request), + this, id, std::move(context)); + bindings_.insert(std::make_pair(id, std::move(entry))); + return id; + } + + void OnConnectionError(BindingId id, + uint32_t custom_reason, + const std::string& description) { + auto it = bindings_.find(id); + DCHECK(it != bindings_.end()); + + // We keep the Entry alive throughout error dispatch. + std::unique_ptr entry = std::move(it->second); + bindings_.erase(it); + + if (!error_handler_.is_null()) + error_handler_.Run(); + else if (!error_with_reason_handler_.is_null()) + error_with_reason_handler_.Run(custom_reason, description); + } + + base::Closure error_handler_; + ConnectionErrorWithReasonCallback error_with_reason_handler_; + PreDispatchCallback pre_dispatch_handler_; + BindingId next_binding_id_ = 0; + std::map> bindings_; + const Context* dispatch_context_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(BindingSetBase); +}; + +template +using BindingSet = BindingSetBase, ContextType>; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_ diff --git a/mojo/public/cpp/bindings/bindings_export.h b/mojo/public/cpp/bindings/bindings_export.h new file mode 100644 index 0000000000000000000000000000000000000000..9fd7a2784eb6a1536556a7d50fa3a9bc206e9cac --- /dev/null +++ b/mojo/public/cpp/bindings/bindings_export.h @@ -0,0 +1,34 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_BINDINGS_EXPORT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_BINDINGS_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_CPP_BINDINGS_IMPLEMENTATION) +#define MOJO_CPP_BINDINGS_EXPORT __declspec(dllexport) +#else +#define MOJO_CPP_BINDINGS_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_CPP_BINDINGS_IMPLEMENTATION) +#define MOJO_CPP_BINDINGS_EXPORT __attribute((visibility("default"))) +#else +#define MOJO_CPP_BINDINGS_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) + +#define MOJO_CPP_BINDINGS_EXPORT + +#endif // defined(COMPONENT_BUILD) + +#endif // MOJO_PUBLIC_CPP_BINDINGS_BINDINGS_EXPORT_H_ diff --git a/mojo/public/cpp/bindings/clone_traits.h b/mojo/public/cpp/bindings/clone_traits.h new file mode 100644 index 0000000000000000000000000000000000000000..203ab34189a5e138a76b1f36eb17ae112cd816c5 --- /dev/null +++ b/mojo/public/cpp/bindings/clone_traits.h @@ -0,0 +1,86 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_CLONE_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_CLONE_TRAITS_H_ + +#include +#include +#include + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { + +template +struct HasCloneMethod { + template + static char Test(decltype(&U::Clone)); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + internal::EnsureTypeIsComplete check_t_; +}; + +template ::value> +struct CloneTraits; + +template +T Clone(const T& input); + +template +struct CloneTraits { + static T Clone(const T& input) { return input.Clone(); } +}; + +template +struct CloneTraits { + static T Clone(const T& input) { return input; } +}; + +template +struct CloneTraits, false> { + static base::Optional Clone(const base::Optional& input) { + if (!input) + return base::nullopt; + + return base::Optional(mojo::Clone(*input)); + } +}; + +template +struct CloneTraits, false> { + static std::vector Clone(const std::vector& input) { + std::vector result; + result.reserve(input.size()); + for (const auto& element : input) + result.push_back(mojo::Clone(element)); + + return result; + } +}; + +template +struct CloneTraits, false> { + static std::unordered_map Clone(const std::unordered_map& input) { + std::unordered_map result; + for (const auto& element : input) { + result.insert(std::make_pair(mojo::Clone(element.first), + mojo::Clone(element.second))); + } + return result; + } +}; + +template +T Clone(const T& input) { + return CloneTraits::Clone(input); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_CLONE_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/connection_error_callback.h b/mojo/public/cpp/bindings/connection_error_callback.h new file mode 100644 index 0000000000000000000000000000000000000000..306e99e45b3462478b759afba74fb794dfa75dc1 --- /dev/null +++ b/mojo/public/cpp/bindings/connection_error_callback.h @@ -0,0 +1,21 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_CONNECTION_ERROR_CALLBACK_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_CONNECTION_ERROR_CALLBACK_H_ + +#include "base/callback.h" + +namespace mojo { + +// This callback type accepts user-defined disconnect reason and description. If +// the other side specifies a reason on closing the connection, it will be +// passed to the error handler. +using ConnectionErrorWithReasonCallback = + base::Callback; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_CONNECTION_ERROR_CALLBACK_H_ diff --git a/mojo/public/cpp/bindings/connector.h b/mojo/public/cpp/bindings/connector.h new file mode 100644 index 0000000000000000000000000000000000000000..cb065c174d7d76b0d8372ffffbb9812960de0ffe --- /dev/null +++ b/mojo/public/cpp/bindings/connector.h @@ -0,0 +1,241 @@ +// Copyright 2013 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 MOJO_PUBLIC_CPP_BINDINGS_CONNECTOR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_CONNECTOR_H_ + +#include + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/sync_handle_watcher.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/simple_watcher.h" + +namespace base { +class Lock; +} + +namespace mojo { + +// The Connector class is responsible for performing read/write operations on a +// MessagePipe. It writes messages it receives through the MessageReceiver +// interface that it subclasses, and it forwards messages it reads through the +// MessageReceiver interface assigned as its incoming receiver. +// +// NOTE: +// - MessagePipe I/O is non-blocking. +// - Sending messages can be configured to be thread safe (please see comments +// of the constructor). Other than that, the object should only be accessed +// on the creating thread. +class MOJO_CPP_BINDINGS_EXPORT Connector + : NON_EXPORTED_BASE(public MessageReceiver) { + public: + enum ConnectorConfig { + // Connector::Accept() is only called from a single thread. + SINGLE_THREADED_SEND, + // Connector::Accept() is allowed to be called from multiple threads. + MULTI_THREADED_SEND + }; + + // The Connector takes ownership of |message_pipe|. + Connector(ScopedMessagePipeHandle message_pipe, + ConnectorConfig config, + scoped_refptr runner); + ~Connector() override; + + // Sets the receiver to handle messages read from the message pipe. The + // Connector will read messages from the pipe regardless of whether or not an + // incoming receiver has been set. + void set_incoming_receiver(MessageReceiver* receiver) { + DCHECK(thread_checker_.CalledOnValidThread()); + incoming_receiver_ = receiver; + } + + // Errors from incoming receivers will force the connector into an error + // state, where no more messages will be processed. This method is used + // during testing to prevent that from happening. + void set_enforce_errors_from_incoming_receiver(bool enforce) { + DCHECK(thread_checker_.CalledOnValidThread()); + enforce_errors_from_incoming_receiver_ = enforce; + } + + // Sets the error handler to receive notifications when an error is + // encountered while reading from the pipe or waiting to read from the pipe. + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + connection_error_handler_ = error_handler; + } + + // Returns true if an error was encountered while reading from the pipe or + // waiting to read from the pipe. + bool encountered_error() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return error_; + } + + // Closes the pipe. The connector is put into a quiescent state. + // + // Please note that this method shouldn't be called unless it results from an + // explicit request of the user of bindings (e.g., the user sets an + // InterfacePtr to null or closes a Binding). + void CloseMessagePipe(); + + // Releases the pipe. Connector is put into a quiescent state. + ScopedMessagePipeHandle PassMessagePipe(); + + // Enters the error state. The upper layer may do this for unrecoverable + // issues such as invalid messages are received. If a connection error handler + // has been set, it will be called asynchronously. + // + // It is a no-op if the connector is already in the error state or there isn't + // a bound message pipe. Otherwise, it closes the message pipe, which notifies + // the other end and also prevents potential danger (say, the caller raises + // an error because it believes the other end is malicious). In order to + // appear to the user that the connector still binds to a message pipe, it + // creates a new message pipe, closes one end and binds to the other. + void RaiseError(); + + // Is the connector bound to a MessagePipe handle? + bool is_valid() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return message_pipe_.is_valid(); + } + + // Waits for the next message on the pipe, blocking until one arrives, + // |deadline| elapses, or an error happens. Returns |true| if a message has + // been delivered, |false| otherwise. + bool WaitForIncomingMessage(MojoDeadline deadline); + + // See Binding for details of pause/resume. + void PauseIncomingMethodCallProcessing(); + void ResumeIncomingMethodCallProcessing(); + + // MessageReceiver implementation: + bool Accept(Message* message) override; + + MessagePipeHandle handle() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return message_pipe_.get(); + } + + // Allows |message_pipe_| to be watched while others perform sync handle + // watching on the same thread. Please see comments of + // SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread(). + void AllowWokenUpBySyncWatchOnSameThread(); + + // Watches |message_pipe_| (as well as other handles registered to be watched + // together) synchronously. + // This method: + // - returns true when |should_stop| is set to true; + // - return false when any error occurs, including |message_pipe_| being + // closed. + bool SyncWatch(const bool* should_stop); + + // Whether currently the control flow is inside the sync handle watcher + // callback. + // It always returns false after CloseMessagePipe()/PassMessagePipe(). + bool during_sync_handle_watcher_callback() const { + return sync_handle_watcher_callback_count_ > 0; + } + + base::SingleThreadTaskRunner* task_runner() const { + return task_runner_.get(); + } + + // Sets the tag used by the heap profiler. + // |tag| must be a const string literal. + void SetWatcherHeapProfilerTag(const char* tag); + + private: + class ActiveDispatchTracker; + class MessageLoopNestingObserver; + + // Callback of mojo::SimpleWatcher. + void OnWatcherHandleReady(MojoResult result); + // Callback of SyncHandleWatcher. + void OnSyncHandleWatcherHandleReady(MojoResult result); + void OnHandleReadyInternal(MojoResult result); + + void WaitToReadMore(); + + // Returns false if it is impossible to receive more messages in the future. + // |this| may have been destroyed in that case. + WARN_UNUSED_RESULT bool ReadSingleMessage(MojoResult* read_result); + + // |this| can be destroyed during message dispatch. + void ReadAllAvailableMessages(); + + // If |force_pipe_reset| is true, this method replaces the existing + // |message_pipe_| with a dummy message pipe handle (whose peer is closed). + // If |force_async_handler| is true, |connection_error_handler_| is called + // asynchronously. + void HandleError(bool force_pipe_reset, bool force_async_handler); + + // Cancels any calls made to |waiter_|. + void CancelWait(); + + void EnsureSyncWatcherExists(); + + base::Closure connection_error_handler_; + + ScopedMessagePipeHandle message_pipe_; + MessageReceiver* incoming_receiver_ = nullptr; + + scoped_refptr task_runner_; + std::unique_ptr handle_watcher_; + + bool error_ = false; + bool drop_writes_ = false; + bool enforce_errors_from_incoming_receiver_ = true; + + bool paused_ = false; + + // If sending messages is allowed from multiple threads, |lock_| is used to + // protect modifications to |message_pipe_| and |drop_writes_|. + base::Optional lock_; + + std::unique_ptr sync_watcher_; + bool allow_woken_up_by_others_ = false; + // If non-zero, currently the control flow is inside the sync handle watcher + // callback. + size_t sync_handle_watcher_callback_count_ = 0; + + base::ThreadChecker thread_checker_; + + base::Lock connected_lock_; + bool connected_ = true; + + // The tag used to track heap allocations that originated from a Watcher + // notification. + const char* heap_profiler_tag_ = nullptr; + + // A cached pointer to the MessageLoopNestingObserver for the MessageLoop on + // which this Connector was created. + MessageLoopNestingObserver* const nesting_observer_; + + // |true| iff the Connector is currently dispatching a message. Used to detect + // nested dispatch operations. + bool is_dispatching_ = false; + + // Create a single weak ptr and use it everywhere, to avoid the malloc/free + // cost of creating a new weak ptr whenever it is needed. + // NOTE: This weak pointer is invalidated when the message pipe is closed or + // transferred (i.e., when |connected_| is set to false). + base::WeakPtr weak_self_; + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(Connector); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_CONNECTOR_H_ diff --git a/mojo/public/cpp/bindings/disconnect_reason.h b/mojo/public/cpp/bindings/disconnect_reason.h new file mode 100644 index 0000000000000000000000000000000000000000..c04e8ada538c18b6525b9997b5c069e9067ebbef --- /dev/null +++ b/mojo/public/cpp/bindings/disconnect_reason.h @@ -0,0 +1,25 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_DISCONNECT_REASON_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_DISCONNECT_REASON_H_ + +#include + +#include + +namespace mojo { + +struct DisconnectReason { + public: + DisconnectReason(uint32_t in_custom_reason, const std::string& in_description) + : custom_reason(in_custom_reason), description(in_description) {} + + uint32_t custom_reason; + std::string description; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_DISCONNECT_REASON_H_ diff --git a/mojo/public/cpp/bindings/enum_traits.h b/mojo/public/cpp/bindings/enum_traits.h new file mode 100644 index 0000000000000000000000000000000000000000..2c528f322683cab2af9851133cb5f1fbe496afb0 --- /dev/null +++ b/mojo/public/cpp/bindings/enum_traits.h @@ -0,0 +1,27 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_ENUM_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ENUM_TRAITS_H_ + +namespace mojo { + +// This must be specialized for any type |T| to be serialized/deserialized as a +// mojom enum |MojomType|. Each specialization needs to implement: +// +// template <> +// struct EnumTraits { +// static MojomType ToMojom(T input); +// +// // Returning false results in deserialization failure and causes the +// // message pipe receiving it to be disconnected. +// static bool FromMojom(MojomType input, T* output); +// }; +// +template +struct EnumTraits; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ENUM_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/filter_chain.h b/mojo/public/cpp/bindings/filter_chain.h new file mode 100644 index 0000000000000000000000000000000000000000..1262f39b8033c7e5e9fd77d0b5aea004403ef042 --- /dev/null +++ b/mojo/public/cpp/bindings/filter_chain.h @@ -0,0 +1,61 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_FILTER_CHAIN_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_FILTER_CHAIN_H_ + +#include +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { + +class MOJO_CPP_BINDINGS_EXPORT FilterChain + : NON_EXPORTED_BASE(public MessageReceiver) { + public: + // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while + // this object is alive. + explicit FilterChain(MessageReceiver* sink = nullptr); + + FilterChain(FilterChain&& other); + FilterChain& operator=(FilterChain&& other); + ~FilterChain() override; + + template + inline void Append(Args&&... args); + + void Append(std::unique_ptr filter); + + // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while + // this object is alive. + void SetSink(MessageReceiver* sink); + + // MessageReceiver: + bool Accept(Message* message) override; + + private: + std::vector> filters_; + + MessageReceiver* sink_; + + DISALLOW_COPY_AND_ASSIGN(FilterChain); +}; + +template +inline void FilterChain::Append(Args&&... args) { + Append(base::MakeUnique(std::forward(args)...)); +} + +template <> +inline void FilterChain::Append() { +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_FILTER_CHAIN_H_ diff --git a/mojo/public/cpp/bindings/interface_data_view.h b/mojo/public/cpp/bindings/interface_data_view.h new file mode 100644 index 0000000000000000000000000000000000000000..ef1225431c9d7eafce78a7bb0fa25764356adb0a --- /dev/null +++ b/mojo/public/cpp/bindings/interface_data_view.h @@ -0,0 +1,25 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_DATA_VIEW_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_DATA_VIEW_H_ + +namespace mojo { + +// They are used for type identification purpose only. +template +class AssociatedInterfacePtrInfoDataView {}; + +template +class AssociatedInterfaceRequestDataView {}; + +template +class InterfacePtrDataView {}; + +template +class InterfaceRequestDataView {}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_DATA_VIEW_H_ diff --git a/mojo/public/cpp/bindings/interface_endpoint_client.h b/mojo/public/cpp/bindings/interface_endpoint_client.h new file mode 100644 index 0000000000000000000000000000000000000000..b519fe92bbbbc79cbc4d1be01954ce4d70827f26 --- /dev/null +++ b/mojo/public/cpp/bindings/interface_endpoint_client.h @@ -0,0 +1,193 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CLIENT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CLIENT_H_ + +#include + +#include +#include + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/disconnect_reason.h" +#include "mojo/public/cpp/bindings/filter_chain.h" +#include "mojo/public/cpp/bindings/lib/control_message_handler.h" +#include "mojo/public/cpp/bindings/lib/control_message_proxy.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +class AssociatedGroup; +class InterfaceEndpointController; + +// InterfaceEndpointClient handles message sending and receiving of an interface +// endpoint, either the implementation side or the client side. +// It should only be accessed and destructed on the creating thread. +class MOJO_CPP_BINDINGS_EXPORT InterfaceEndpointClient + : NON_EXPORTED_BASE(public MessageReceiverWithResponder) { + public: + // |receiver| is okay to be null. If it is not null, it must outlive this + // object. + InterfaceEndpointClient(ScopedInterfaceEndpointHandle handle, + MessageReceiverWithResponderStatus* receiver, + std::unique_ptr payload_validator, + bool expect_sync_requests, + scoped_refptr runner, + uint32_t interface_version); + ~InterfaceEndpointClient() override; + + // Sets the error handler to receive notifications when an error is + // encountered. + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + error_handler_ = error_handler; + error_with_reason_handler_.Reset(); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + error_with_reason_handler_ = error_handler; + error_handler_.Reset(); + } + + // Returns true if an error was encountered. + bool encountered_error() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return encountered_error_; + } + + // Returns true if this endpoint has any pending callbacks. + bool has_pending_responders() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return !async_responders_.empty() || !sync_responses_.empty(); + } + + AssociatedGroup* associated_group(); + + // Adds a MessageReceiver which can filter a message after validation but + // before dispatch. + void AddFilter(std::unique_ptr filter); + + // After this call the object is in an invalid state and shouldn't be reused. + ScopedInterfaceEndpointHandle PassHandle(); + + // Raises an error on the underlying message pipe. It disconnects the pipe + // and notifies all interfaces running on this pipe. + void RaiseError(); + + void CloseWithReason(uint32_t custom_reason, const std::string& description); + + // MessageReceiverWithResponder implementation: + // They must only be called when the handle is not in pending association + // state. + bool Accept(Message* message) override; + bool AcceptWithResponder(Message* message, + std::unique_ptr responder) override; + + // The following methods are called by the router. They must be called + // outside of the router's lock. + + // NOTE: |message| must have passed message header validation. + bool HandleIncomingMessage(Message* message); + void NotifyError(const base::Optional& reason); + + // The following methods send interface control messages. + // They must only be called when the handle is not in pending association + // state. + void QueryVersion(const base::Callback& callback); + void RequireVersion(uint32_t version); + void FlushForTesting(); + + private: + // Maps from the id of a response to the MessageReceiver that handles the + // response. + using AsyncResponderMap = + std::map>; + + struct SyncResponseInfo { + public: + explicit SyncResponseInfo(bool* in_response_received); + ~SyncResponseInfo(); + + Message response; + + // Points to a stack-allocated variable. + bool* response_received; + + private: + DISALLOW_COPY_AND_ASSIGN(SyncResponseInfo); + }; + + using SyncResponseMap = std::map>; + + // Used as the sink for |payload_validator_| and forwards messages to + // HandleValidatedMessage(). + class HandleIncomingMessageThunk : public MessageReceiver { + public: + explicit HandleIncomingMessageThunk(InterfaceEndpointClient* owner); + ~HandleIncomingMessageThunk() override; + + // MessageReceiver implementation: + bool Accept(Message* message) override; + + private: + InterfaceEndpointClient* const owner_; + + DISALLOW_COPY_AND_ASSIGN(HandleIncomingMessageThunk); + }; + + void InitControllerIfNecessary(); + + void OnAssociationEvent( + ScopedInterfaceEndpointHandle::AssociationEvent event); + + bool HandleValidatedMessage(Message* message); + + const bool expect_sync_requests_ = false; + + ScopedInterfaceEndpointHandle handle_; + std::unique_ptr associated_group_; + InterfaceEndpointController* controller_ = nullptr; + + MessageReceiverWithResponderStatus* const incoming_receiver_ = nullptr; + HandleIncomingMessageThunk thunk_; + FilterChain filters_; + + AsyncResponderMap async_responders_; + SyncResponseMap sync_responses_; + + uint64_t next_request_id_ = 1; + + base::Closure error_handler_; + ConnectionErrorWithReasonCallback error_with_reason_handler_; + bool encountered_error_ = false; + + scoped_refptr task_runner_; + + internal::ControlMessageProxy control_message_proxy_; + internal::ControlMessageHandler control_message_handler_; + + base::ThreadChecker thread_checker_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(InterfaceEndpointClient); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CLIENT_H_ diff --git a/mojo/public/cpp/bindings/interface_endpoint_controller.h b/mojo/public/cpp/bindings/interface_endpoint_controller.h new file mode 100644 index 0000000000000000000000000000000000000000..8d99d4a45fb098379d4f860d92558fb9c7642a2b --- /dev/null +++ b/mojo/public/cpp/bindings/interface_endpoint_controller.h @@ -0,0 +1,37 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CONTROLLER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CONTROLLER_H_ + +namespace mojo { + +class Message; + +// A control interface exposed by AssociatedGroupController for interface +// endpoints. +class InterfaceEndpointController { + public: + virtual ~InterfaceEndpointController() {} + + virtual bool SendMessage(Message* message) = 0; + + // Allows the interface endpoint to watch for incoming sync messages while + // others perform sync handle watching on the same thread. Please see comments + // of SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread(). + virtual void AllowWokenUpBySyncWatchOnSameThread() = 0; + + // Watches the interface endpoint for incoming sync messages. (It also watches + // other other handles registered to be watched together.) + // This method: + // - returns true when |should_stop| is set to true; + // - return false otherwise, including + // MultiplexRouter::DetachEndpointClient() being called for the same + // interface endpoint. + virtual bool SyncWatch(const bool* should_stop) = 0; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CONTROLLER_H_ diff --git a/mojo/public/cpp/bindings/interface_id.h b/mojo/public/cpp/bindings/interface_id.h new file mode 100644 index 0000000000000000000000000000000000000000..53475d6f789ff7eeb4170dde4dfb1cf1bbb7c6ef --- /dev/null +++ b/mojo/public/cpp/bindings/interface_id.h @@ -0,0 +1,35 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ID_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ID_H_ + +#include + +namespace mojo { + +// The size of the type matters because it is directly used in messages. +using InterfaceId = uint32_t; + +// IDs of associated interface can be generated at both sides of the message +// pipe. In order to avoid collision, the highest bit is used as namespace bit: +// at the side where the client-side of the master interface lives, IDs are +// generated with the namespace bit set to 1; at the opposite side IDs are +// generated with the namespace bit set to 0. +const uint32_t kInterfaceIdNamespaceMask = 0x80000000; + +const InterfaceId kMasterInterfaceId = 0x00000000; +const InterfaceId kInvalidInterfaceId = 0xFFFFFFFF; + +inline bool IsMasterInterfaceId(InterfaceId id) { + return id == kMasterInterfaceId; +} + +inline bool IsValidInterfaceId(InterfaceId id) { + return id != kInvalidInterfaceId; +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ID_H_ diff --git a/mojo/public/cpp/bindings/interface_ptr.h b/mojo/public/cpp/bindings/interface_ptr.h new file mode 100644 index 0000000000000000000000000000000000000000..e88be7436fb4c86b00a368bd4073b122d01272d9 --- /dev/null +++ b/mojo/public/cpp/bindings/interface_ptr.h @@ -0,0 +1,242 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ + +#include + +#include +#include + +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/lib/interface_ptr_state.h" + +namespace mojo { + +// A pointer to a local proxy of a remote Interface implementation. Uses a +// message pipe to communicate with the remote implementation, and automatically +// closes the pipe and deletes the proxy on destruction. The pointer must be +// bound to a message pipe before the interface methods can be called. +// +// This class is thread hostile, as is the local proxy it manages, while bound +// to a message pipe. All calls to this class or the proxy should be from the +// same thread that bound it. If you need to move the proxy to a different +// thread, extract the InterfacePtrInfo (containing just the message pipe and +// any version information) using PassInterface(), pass it to a different +// thread, and create and bind a new InterfacePtr from that thread. If an +// InterfacePtr is not bound to a message pipe, it may be bound or destroyed on +// any thread. +template +class InterfacePtr { + public: + using InterfaceType = Interface; + using PtrInfoType = InterfacePtrInfo; + + // Constructs an unbound InterfacePtr. + InterfacePtr() {} + InterfacePtr(decltype(nullptr)) {} + + // Takes over the binding of another InterfacePtr. + InterfacePtr(InterfacePtr&& other) { + internal_state_.Swap(&other.internal_state_); + } + + // Takes over the binding of another InterfacePtr, and closes any message pipe + // already bound to this pointer. + InterfacePtr& operator=(InterfacePtr&& other) { + reset(); + internal_state_.Swap(&other.internal_state_); + return *this; + } + + // Assigning nullptr to this class causes it to close the currently bound + // message pipe (if any) and returns the pointer to the unbound state. + InterfacePtr& operator=(decltype(nullptr)) { + reset(); + return *this; + } + + // Closes the bound message pipe (if any) on destruction. + ~InterfacePtr() {} + + // Binds the InterfacePtr to a remote implementation of Interface. + // + // Calling with an invalid |info| (containing an invalid message pipe handle) + // has the same effect as reset(). In this case, the InterfacePtr is not + // considered as bound. + // + // |runner| must belong to the same thread. It will be used to dispatch all + // callbacks and connection error notification. It is useful when you attach + // multiple task runners to a single thread for the purposes of task + // scheduling. + void Bind(InterfacePtrInfo info, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + reset(); + if (info.is_valid()) + internal_state_.Bind(std::move(info), std::move(runner)); + } + + // Returns whether or not this InterfacePtr is bound to a message pipe. + bool is_bound() const { return internal_state_.is_bound(); } + + // Returns a raw pointer to the local proxy. Caller does not take ownership. + // Note that the local proxy is thread hostile, as stated above. + Interface* get() const { return internal_state_.instance(); } + + // Functions like a pointer to Interface. Must already be bound. + Interface* operator->() const { return get(); } + Interface& operator*() const { return *get(); } + + // Returns the version number of the interface that the remote side supports. + uint32_t version() const { return internal_state_.version(); } + + // Queries the max version that the remote side supports. On completion, the + // result will be returned as the input of |callback|. The version number of + // this interface pointer will also be updated. + void QueryVersion(const base::Callback& callback) { + internal_state_.QueryVersion(callback); + } + + // If the remote side doesn't support the specified version, it will close its + // end of the message pipe asynchronously. This does nothing if it's already + // known that the remote side supports the specified version, i.e., if + // |version <= this->version()|. + // + // After calling RequireVersion() with a version not supported by the remote + // side, all subsequent calls to interface methods will be ignored. + void RequireVersion(uint32_t version) { + internal_state_.RequireVersion(version); + } + + // Sends a no-op message on the underlying message pipe and runs the current + // message loop until its response is received. This can be used in tests to + // verify that no message was sent on a message pipe in response to some + // stimulus. + void FlushForTesting() { internal_state_.FlushForTesting(); } + + // Closes the bound message pipe (if any) and returns the pointer to the + // unbound state. + void reset() { + State doomed; + internal_state_.Swap(&doomed); + } + + // Similar to the method above, but also specifies a disconnect reason. + void ResetWithReason(uint32_t custom_reason, const std::string& description) { + if (internal_state_.is_bound()) + internal_state_.CloseWithReason(custom_reason, description); + reset(); + } + + // Whether there are any associated interfaces running on the pipe currently. + bool HasAssociatedInterfaces() const { + return internal_state_.HasAssociatedInterfaces(); + } + + // Indicates whether the message pipe has encountered an error. If true, + // method calls made on this interface will be dropped (and may already have + // been dropped). + bool encountered_error() const { return internal_state_.encountered_error(); } + + // Registers a handler to receive error notifications. The handler will be + // called from the thread that owns this InterfacePtr. + // + // This method may only be called after the InterfacePtr has been bound to a + // message pipe. + void set_connection_error_handler(const base::Closure& error_handler) { + internal_state_.set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + internal_state_.set_connection_error_with_reason_handler(error_handler); + } + + // Unbinds the InterfacePtr and returns the information which could be used + // to setup an InterfacePtr again. This method may be used to move the proxy + // to a different thread (see class comments for details). + // + // It is an error to call PassInterface() while: + // - there are pending responses; or + // TODO: fix this restriction, it's not always obvious when there is a + // pending response. + // - there are associated interfaces running. + // TODO(yzshen): For now, users need to make sure there is no one holding + // on to associated interface endpoint handles at both sides of the + // message pipe in order to call this method. We need a way to forcefully + // invalidate associated interface endpoint handles. + InterfacePtrInfo PassInterface() { + CHECK(!HasAssociatedInterfaces()); + CHECK(!internal_state_.has_pending_callbacks()); + State state; + internal_state_.Swap(&state); + + return state.PassInterface(); + } + + bool Equals(const InterfacePtr& other) const { + if (this == &other) + return true; + + // Now that the two refer to different objects, they are equivalent if + // and only if they are both null. + return !(*this) && !other; + } + + // DO NOT USE. Exposed only for internal use and for testing. + internal::InterfacePtrState* internal_state() { + return &internal_state_; + } + + // Allow InterfacePtr<> to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + private: + // TODO(dcheng): Use an explicit conversion operator. + typedef internal::InterfacePtrState InterfacePtr::*Testable; + + public: + operator Testable() const { + return internal_state_.is_bound() ? &InterfacePtr::internal_state_ + : nullptr; + } + + private: + // Forbid the == and != operators explicitly, otherwise InterfacePtr will be + // converted to Testable to do == or != comparison. + template + bool operator==(const InterfacePtr& other) const = delete; + template + bool operator!=(const InterfacePtr& other) const = delete; + + typedef internal::InterfacePtrState State; + mutable State internal_state_; + + DISALLOW_COPY_AND_ASSIGN(InterfacePtr); +}; + +// If |info| is valid (containing a valid message pipe handle), returns an +// InterfacePtr bound to it. Otherwise, returns an unbound InterfacePtr. +template +InterfacePtr MakeProxy( + InterfacePtrInfo info, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + InterfacePtr ptr; + if (info.is_valid()) + ptr.Bind(std::move(info), std::move(runner)); + return std::move(ptr); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ diff --git a/mojo/public/cpp/bindings/interface_ptr_info.h b/mojo/public/cpp/bindings/interface_ptr_info.h new file mode 100644 index 0000000000000000000000000000000000000000..0b2d8089c4327f7bf608c152980d882dc0626a6b --- /dev/null +++ b/mojo/public/cpp/bindings/interface_ptr_info.h @@ -0,0 +1,63 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_ + +#include +#include + +#include "base/macros.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace mojo { + +// InterfacePtrInfo stores necessary information to communicate with a remote +// interface implementation, which could be used to construct an InterfacePtr. +template +class InterfacePtrInfo { + public: + InterfacePtrInfo() : version_(0u) {} + + InterfacePtrInfo(ScopedMessagePipeHandle handle, uint32_t version) + : handle_(std::move(handle)), version_(version) {} + + InterfacePtrInfo(InterfacePtrInfo&& other) + : handle_(std::move(other.handle_)), version_(other.version_) { + other.version_ = 0u; + } + + ~InterfacePtrInfo() {} + + InterfacePtrInfo& operator=(InterfacePtrInfo&& other) { + if (this != &other) { + handle_ = std::move(other.handle_); + version_ = other.version_; + other.version_ = 0u; + } + + return *this; + } + + bool is_valid() const { return handle_.is_valid(); } + + ScopedMessagePipeHandle PassHandle() { return std::move(handle_); } + const ScopedMessagePipeHandle& handle() const { return handle_; } + void set_handle(ScopedMessagePipeHandle handle) { + handle_ = std::move(handle); + } + + uint32_t version() const { return version_; } + void set_version(uint32_t version) { version_ = version; } + + private: + ScopedMessagePipeHandle handle_; + uint32_t version_; + + DISALLOW_COPY_AND_ASSIGN(InterfacePtrInfo); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_ diff --git a/mojo/public/cpp/bindings/interface_ptr_set.h b/mojo/public/cpp/bindings/interface_ptr_set.h new file mode 100644 index 0000000000000000000000000000000000000000..09a268229dd93f501c94105311fd843452ce8662 --- /dev/null +++ b/mojo/public/cpp/bindings/interface_ptr_set.h @@ -0,0 +1,107 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_SET_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_SET_H_ + +#include +#include + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" + +namespace mojo { +namespace internal { + +// TODO(blundell): This class should be rewritten to be structured +// similarly to BindingSet if possible, with PtrSet owning its +// Elements and those Elements calling back into PtrSet on connection +// error. +template class Ptr> +class PtrSet { + public: + PtrSet() {} + ~PtrSet() { CloseAll(); } + + void AddPtr(Ptr ptr) { + auto weak_interface_ptr = new Element(std::move(ptr)); + ptrs_.push_back(weak_interface_ptr->GetWeakPtr()); + ClearNullPtrs(); + } + + template + void ForAllPtrs(FunctionType function) { + for (const auto& it : ptrs_) { + if (it) + function(it->get()); + } + ClearNullPtrs(); + } + + void CloseAll() { + for (const auto& it : ptrs_) { + if (it) + it->Close(); + } + ptrs_.clear(); + } + + private: + class Element { + public: + explicit Element(Ptr ptr) + : ptr_(std::move(ptr)), weak_ptr_factory_(this) { + ptr_.set_connection_error_handler(base::Bind(&DeleteElement, this)); + } + + ~Element() {} + + void Close() { + ptr_.reset(); + + // Resetting the interface ptr means that it won't call this object back + // on connection error anymore, so this object must delete itself now. + DeleteElement(this); + } + + Interface* get() { return ptr_.get(); } + + base::WeakPtr GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); + } + + private: + static void DeleteElement(Element* element) { delete element; } + + Ptr ptr_; + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(Element); + }; + + void ClearNullPtrs() { + ptrs_.erase(std::remove_if(ptrs_.begin(), ptrs_.end(), + [](const base::WeakPtr& p) { + return p.get() == nullptr; + }), + ptrs_.end()); + } + + std::vector> ptrs_; +}; + +} // namespace internal + +template +using InterfacePtrSet = internal::PtrSet; + +template +using AssociatedInterfacePtrSet = + internal::PtrSet; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_SET_H_ diff --git a/mojo/public/cpp/bindings/interface_request.h b/mojo/public/cpp/bindings/interface_request.h new file mode 100644 index 0000000000000000000000000000000000000000..29d883615e6a8638fa7f89f814d1fa5b5164a67a --- /dev/null +++ b/mojo/public/cpp/bindings/interface_request.h @@ -0,0 +1,178 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ + +#include +#include + +#include "base/macros.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/disconnect_reason.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/pipe_control_message_proxy.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace mojo { + +// Represents a request from a remote client for an implementation of Interface +// over a specified message pipe. The implementor of the interface should +// remove the message pipe by calling PassMessagePipe() and bind it to the +// implementation. If this is not done, the InterfaceRequest will automatically +// close the pipe on destruction. Can also represent the absence of a request +// if the client did not provide a message pipe. +template +class InterfaceRequest { + public: + // Constructs an empty InterfaceRequest, representing that the client is not + // requesting an implementation of Interface. + InterfaceRequest() {} + InterfaceRequest(decltype(nullptr)) {} + + // Creates a new message pipe over which Interface is to be served, binding + // the specified InterfacePtr to one end of the message pipe and this + // InterfaceRequest to the other. For example usage, see comments on + // MakeRequest(InterfacePtr*) below. + explicit InterfaceRequest(InterfacePtr* ptr, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + MessagePipe pipe; + ptr->Bind(InterfacePtrInfo(std::move(pipe.handle0), 0u), + std::move(runner)); + Bind(std::move(pipe.handle1)); + } + + // Takes the message pipe from another InterfaceRequest. + InterfaceRequest(InterfaceRequest&& other) { + handle_ = std::move(other.handle_); + } + InterfaceRequest& operator=(InterfaceRequest&& other) { + handle_ = std::move(other.handle_); + return *this; + } + + // Assigning to nullptr resets the InterfaceRequest to an empty state, + // closing the message pipe currently bound to it (if any). + InterfaceRequest& operator=(decltype(nullptr)) { + handle_.reset(); + return *this; + } + + // Binds the request to a message pipe over which Interface is to be + // requested. If the request is already bound to a message pipe, the current + // message pipe will be closed. + void Bind(ScopedMessagePipeHandle handle) { handle_ = std::move(handle); } + + // Indicates whether the request currently contains a valid message pipe. + bool is_pending() const { return handle_.is_valid(); } + + // Removes the message pipe from the request and returns it. + ScopedMessagePipeHandle PassMessagePipe() { return std::move(handle_); } + + bool Equals(const InterfaceRequest& other) const { + if (this == &other) + return true; + + // Now that the two refer to different objects, they are equivalent if + // and only if they are both invalid. + return !is_pending() && !other.is_pending(); + } + + void ResetWithReason(uint32_t custom_reason, const std::string& description) { + if (!handle_.is_valid()) + return; + + Message message = + PipeControlMessageProxy::ConstructPeerEndpointClosedMessage( + kMasterInterfaceId, DisconnectReason(custom_reason, description)); + MojoResult result = WriteMessageNew( + handle_.get(), message.TakeMojoMessage(), MOJO_WRITE_MESSAGE_FLAG_NONE); + DCHECK_EQ(MOJO_RESULT_OK, result); + + handle_.reset(); + } + + private: + ScopedMessagePipeHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(InterfaceRequest); +}; + +// Makes an InterfaceRequest bound to the specified message pipe. If |handle| +// is empty or invalid, the resulting InterfaceRequest will represent the +// absence of a request. +template +InterfaceRequest MakeRequest(ScopedMessagePipeHandle handle) { + InterfaceRequest request; + request.Bind(std::move(handle)); + return std::move(request); +} + +// Creates a new message pipe over which Interface is to be served. Binds the +// specified InterfacePtr to one end of the message pipe, and returns an +// InterfaceRequest bound to the other. The InterfacePtr should be passed to +// the client, and the InterfaceRequest should be passed to whatever will +// provide the implementation. The implementation should typically be bound to +// the InterfaceRequest using the Binding or StrongBinding classes. The client +// may begin to issue calls even before an implementation has been bound, since +// messages sent over the pipe will just queue up until they are consumed by +// the implementation. +// +// Example #1: Requesting a remote implementation of an interface. +// =============================================================== +// +// Given the following interface: +// +// interface Database { +// OpenTable(Table& table); +// } +// +// The client would have code similar to the following: +// +// DatabasePtr database = ...; // Connect to database. +// TablePtr table; +// database->OpenTable(MakeRequest(&table)); +// +// Upon return from MakeRequest, |table| is ready to have methods called on it. +// +// Example #2: Registering a local implementation with a remote service. +// ===================================================================== +// +// Given the following interface +// interface Collector { +// RegisterSource(Source source); +// } +// +// The client would have code similar to the following: +// +// CollectorPtr collector = ...; // Connect to Collector. +// SourcePtr source; +// InterfaceRequest source_request(&source); +// collector->RegisterSource(std::move(source)); +// CreateSource(std::move(source_request)); // Create implementation locally. +// +template +InterfaceRequest MakeRequest( + InterfacePtr* ptr, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + return InterfaceRequest(ptr, runner); +} + +// Fuses an InterfaceRequest endpoint with an InterfacePtrInfo endpoint. +// Returns |true| on success or |false| on failure. +template +bool FuseInterface(InterfaceRequest request, + InterfacePtrInfo proxy_info) { + MojoResult result = FuseMessagePipes(request.PassMessagePipe(), + proxy_info.PassHandle()); + return result == MOJO_RESULT_OK; +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ diff --git a/mojo/public/cpp/bindings/lib/array_internal.cc b/mojo/public/cpp/bindings/lib/array_internal.cc new file mode 100644 index 0000000000000000000000000000000000000000..dd24eac4702f0d15c045f4a303c59ee19e4c5268 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/array_internal.cc @@ -0,0 +1,59 @@ +// Copyright 2013 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. + +#include "mojo/public/cpp/bindings/lib/array_internal.h" + +#include +#include + +#include + +namespace mojo { +namespace internal { + +std::string MakeMessageWithArrayIndex(const char* message, + size_t size, + size_t index) { + std::ostringstream stream; + stream << message << ": array size - " << size << "; index - " << index; + return stream.str(); +} + +std::string MakeMessageWithExpectedArraySize(const char* message, + size_t size, + size_t expected_size) { + std::ostringstream stream; + stream << message << ": array size - " << size << "; expected size - " + << expected_size; + return stream.str(); +} + +ArrayDataTraits::BitRef::~BitRef() { +} + +ArrayDataTraits::BitRef::BitRef(uint8_t* storage, uint8_t mask) + : storage_(storage), mask_(mask) { +} + +ArrayDataTraits::BitRef& ArrayDataTraits::BitRef::operator=( + bool value) { + if (value) { + *storage_ |= mask_; + } else { + *storage_ &= ~mask_; + } + return *this; +} + +ArrayDataTraits::BitRef& ArrayDataTraits::BitRef::operator=( + const BitRef& value) { + return (*this) = static_cast(value); +} + +ArrayDataTraits::BitRef::operator bool() const { + return (*storage_ & mask_) != 0; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/array_internal.h b/mojo/public/cpp/bindings/lib/array_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..eecfcfbc2851491e0c0eca7ae5c4a383590b4ab0 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/array_internal.h @@ -0,0 +1,368 @@ +// Copyright 2013 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ + +#include +#include + +#include +#include + +#include "base/logging.h" +#include "mojo/public/c/system/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" + +namespace mojo { +namespace internal { + +template +class Map_Data; + +MOJO_CPP_BINDINGS_EXPORT std::string +MakeMessageWithArrayIndex(const char* message, size_t size, size_t index); + +MOJO_CPP_BINDINGS_EXPORT std::string MakeMessageWithExpectedArraySize( + const char* message, + size_t size, + size_t expected_size); + +template +struct ArrayDataTraits { + using StorageType = T; + using Ref = T&; + using ConstRef = const T&; + + static const uint32_t kMaxNumElements = + (std::numeric_limits::max() - sizeof(ArrayHeader)) / + sizeof(StorageType); + + static uint32_t GetStorageSize(uint32_t num_elements) { + DCHECK(num_elements <= kMaxNumElements); + return sizeof(ArrayHeader) + sizeof(StorageType) * num_elements; + } + static Ref ToRef(StorageType* storage, size_t offset) { + return storage[offset]; + } + static ConstRef ToConstRef(const StorageType* storage, size_t offset) { + return storage[offset]; + } +}; + +// Specialization of Arrays for bools, optimized for space. It has the +// following differences from a generalized Array: +// * Each element takes up a single bit of memory. +// * Accessing a non-const single element uses a helper class |BitRef|, which +// emulates a reference to a bool. +template <> +struct ArrayDataTraits { + // Helper class to emulate a reference to a bool, used for direct element + // access. + class MOJO_CPP_BINDINGS_EXPORT BitRef { + public: + ~BitRef(); + BitRef& operator=(bool value); + BitRef& operator=(const BitRef& value); + operator bool() const; + + private: + friend struct ArrayDataTraits; + BitRef(uint8_t* storage, uint8_t mask); + BitRef(); + uint8_t* storage_; + uint8_t mask_; + }; + + // Because each element consumes only 1/8 byte. + static const uint32_t kMaxNumElements = std::numeric_limits::max(); + + using StorageType = uint8_t; + using Ref = BitRef; + using ConstRef = bool; + + static uint32_t GetStorageSize(uint32_t num_elements) { + return sizeof(ArrayHeader) + ((num_elements + 7) / 8); + } + static BitRef ToRef(StorageType* storage, size_t offset) { + return BitRef(&storage[offset / 8], 1 << (offset % 8)); + } + static bool ToConstRef(const StorageType* storage, size_t offset) { + return (storage[offset / 8] & (1 << (offset % 8))) != 0; + } +}; + +// What follows is code to support the serialization/validation of +// Array_Data. There are four interesting cases: arrays of primitives, +// arrays of handles/interfaces, arrays of objects and arrays of unions. +// Arrays of objects are represented as arrays of pointers to objects. Arrays +// of unions are inlined so they are not pointers, but comparing with primitives +// they require more work for serialization/validation. +// +// TODO(yzshen): Validation code should be organzied in a way similar to +// Serializer<>, or merged into it. It should be templatized with the mojo +// data view type instead of the data type, that way we can use MojomTypeTraits +// to determine the categories. + +template +struct ArraySerializationHelper; + +template +struct ArraySerializationHelper { + using ElementType = typename ArrayDataTraits::StorageType; + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + DCHECK(!validate_params->element_is_nullable) + << "Primitive type should be non-nullable"; + DCHECK(!validate_params->element_validate_params) + << "Primitive type should not have array validate params"; + + if (!validate_params->validate_enum_func) + return true; + + // Enum validation. + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!validate_params->validate_enum_func(elements[i], validation_context)) + return false; + } + return true; + } +}; + +template +struct ArraySerializationHelper { + using ElementType = typename ArrayDataTraits::StorageType; + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + DCHECK(!validate_params->element_validate_params) + << "Handle or interface type should not have array validate params"; + + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!validate_params->element_is_nullable && + !IsHandleOrInterfaceValid(elements[i])) { + static const ValidationError kError = + std::is_same::value || + std::is_same::value + ? VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE + : VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID; + ReportValidationError( + validation_context, kError, + MakeMessageWithArrayIndex( + "invalid handle or interface ID in array expecting valid " + "handles or interface IDs", + header->num_elements, i) + .c_str()); + return false; + } + if (!ValidateHandleOrInterface(elements[i], validation_context)) + return false; + } + return true; + } +}; + +template +struct ArraySerializationHelper, false, false> { + using ElementType = typename ArrayDataTraits>::StorageType; + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!validate_params->element_is_nullable && !elements[i].offset) { + ReportValidationError( + validation_context, + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid pointers", + header->num_elements, + i).c_str()); + return false; + } + if (!ValidateCaller::Run(elements[i], validation_context, + validate_params->element_validate_params)) { + return false; + } + } + return true; + } + + private: + template ::value || + IsSpecializationOf::value> + struct ValidateCaller { + static bool Run(const Pointer& data, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + DCHECK(!validate_params) + << "Struct type should not have array validate params"; + + return ValidateStruct(data, validation_context); + } + }; + + template + struct ValidateCaller { + static bool Run(const Pointer& data, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + return ValidateContainer(data, validation_context, validate_params); + } + }; +}; + +template +struct ArraySerializationHelper { + using ElementType = typename ArrayDataTraits::StorageType; + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!validate_params->element_is_nullable && elements[i].is_null()) { + ReportValidationError( + validation_context, + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid unions", + header->num_elements, i) + .c_str()); + return false; + } + if (!ValidateInlinedUnion(elements[i], validation_context)) + return false; + } + return true; + } +}; + +template +class Array_Data { + public: + using Traits = ArrayDataTraits; + using StorageType = typename Traits::StorageType; + using Ref = typename Traits::Ref; + using ConstRef = typename Traits::ConstRef; + using Helper = ArraySerializationHelper< + T, + IsUnionDataType::value, + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value>; + using Element = T; + + // Returns null if |num_elements| or the corresponding storage size cannot be + // stored in uint32_t. + static Array_Data* New(size_t num_elements, Buffer* buf) { + if (num_elements > Traits::kMaxNumElements) + return nullptr; + + uint32_t num_bytes = + Traits::GetStorageSize(static_cast(num_elements)); + return new (buf->Allocate(num_bytes)) + Array_Data(num_bytes, static_cast(num_elements)); + } + + static bool Validate(const void* data, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + if (!data) + return true; + if (!IsAligned(data)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_MISALIGNED_OBJECT); + return false; + } + if (!validation_context->IsValidRange(data, sizeof(ArrayHeader))) { + ReportValidationError(validation_context, + VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + const ArrayHeader* header = static_cast(data); + if (header->num_elements > Traits::kMaxNumElements || + header->num_bytes < Traits::GetStorageSize(header->num_elements)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER); + return false; + } + if (validate_params->expected_num_elements != 0 && + header->num_elements != validate_params->expected_num_elements) { + ReportValidationError( + validation_context, + VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + MakeMessageWithExpectedArraySize( + "fixed-size array has wrong number of elements", + header->num_elements, + validate_params->expected_num_elements).c_str()); + return false; + } + if (!validation_context->ClaimMemory(data, header->num_bytes)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + + const Array_Data* object = static_cast*>(data); + return Helper::ValidateElements(&object->header_, object->storage(), + validation_context, validate_params); + } + + size_t size() const { return header_.num_elements; } + + Ref at(size_t offset) { + DCHECK(offset < static_cast(header_.num_elements)); + return Traits::ToRef(storage(), offset); + } + + ConstRef at(size_t offset) const { + DCHECK(offset < static_cast(header_.num_elements)); + return Traits::ToConstRef(storage(), offset); + } + + StorageType* storage() { + return reinterpret_cast(reinterpret_cast(this) + + sizeof(*this)); + } + + const StorageType* storage() const { + return reinterpret_cast( + reinterpret_cast(this) + sizeof(*this)); + } + + private: + Array_Data(uint32_t num_bytes, uint32_t num_elements) { + header_.num_bytes = num_bytes; + header_.num_elements = num_elements; + } + ~Array_Data() = delete; + + internal::ArrayHeader header_; + + // Elements of type internal::ArrayDataTraits::StorageType follow. +}; +static_assert(sizeof(Array_Data) == 8, "Bad sizeof(Array_Data)"); + +// UTF-8 encoded +using String_Data = Array_Data; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/array_serialization.h b/mojo/public/cpp/bindings/lib/array_serialization.h new file mode 100644 index 0000000000000000000000000000000000000000..d2f8ecfd725cb2c6f6e3ec456354f4d3e999e243 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/array_serialization.h @@ -0,0 +1,555 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ + +#include +#include // For |memcpy()|. + +#include +#include +#include +#include + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/array_data_view.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" + +namespace mojo { +namespace internal { + +template ::value> +class ArrayIterator {}; + +// Used as the UserTypeIterator template parameter of ArraySerializer. +template +class ArrayIterator { + public: + using IteratorType = decltype( + CallGetBeginIfExists(std::declval())); + + explicit ArrayIterator(MaybeConstUserType& input) + : input_(input), iter_(CallGetBeginIfExists(input)) {} + ~ArrayIterator() {} + + size_t GetSize() const { return Traits::GetSize(input_); } + + using GetNextResult = + decltype(Traits::GetValue(std::declval())); + GetNextResult GetNext() { + GetNextResult value = Traits::GetValue(iter_); + Traits::AdvanceIterator(iter_); + return value; + } + + using GetDataIfExistsResult = decltype( + CallGetDataIfExists(std::declval())); + GetDataIfExistsResult GetDataIfExists() { + return CallGetDataIfExists(input_); + } + + private: + MaybeConstUserType& input_; + IteratorType iter_; +}; + +// Used as the UserTypeIterator template parameter of ArraySerializer. +template +class ArrayIterator { + public: + explicit ArrayIterator(MaybeConstUserType& input) : input_(input), iter_(0) {} + ~ArrayIterator() {} + + size_t GetSize() const { return Traits::GetSize(input_); } + + using GetNextResult = + decltype(Traits::GetAt(std::declval(), 0)); + GetNextResult GetNext() { + DCHECK_LT(iter_, Traits::GetSize(input_)); + return Traits::GetAt(input_, iter_++); + } + + using GetDataIfExistsResult = decltype( + CallGetDataIfExists(std::declval())); + GetDataIfExistsResult GetDataIfExists() { + return CallGetDataIfExists(input_); + } + + private: + MaybeConstUserType& input_; + size_t iter_; +}; + +// ArraySerializer is also used to serialize map keys and values. Therefore, it +// has a UserTypeIterator parameter which is an adaptor for reading to hide the +// difference between ArrayTraits and MapTraits. +template +struct ArraySerializer; + +// Handles serialization and deserialization of arrays of pod types. +template +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if::value>::type> { + using UserType = typename std::remove_const::type; + using Data = typename MojomTypeTraits::Data; + using DataElement = typename Data::Element; + using Element = typename MojomType::Element; + using Traits = ArrayTraits; + + static_assert(std::is_same::value, + "Incorrect array serializer"); + static_assert(std::is_same::value, + "Incorrect array serializer"); + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + return sizeof(Data) + Align(input->GetSize() * sizeof(DataElement)); + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + DCHECK(!validate_params->element_is_nullable) + << "Primitive type should be non-nullable"; + DCHECK(!validate_params->element_validate_params) + << "Primitive type should not have array validate params"; + + size_t size = input->GetSize(); + if (size == 0) + return; + + auto data = input->GetDataIfExists(); + if (data) { + memcpy(output->storage(), data, size * sizeof(DataElement)); + } else { + for (size_t i = 0; i < size; ++i) + output->at(i) = input->GetNext(); + } + } + + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator iterator(*output); + if (input->size()) { + auto data = iterator.GetDataIfExists(); + if (data) { + memcpy(data, input->storage(), input->size() * sizeof(DataElement)); + } else { + for (size_t i = 0; i < input->size(); ++i) + iterator.GetNext() = input->at(i); + } + } + return true; + } +}; + +// Handles serialization and deserialization of arrays of enum types. +template +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if::value>::type> { + using UserType = typename std::remove_const::type; + using Data = typename MojomTypeTraits::Data; + using DataElement = typename Data::Element; + using Element = typename MojomType::Element; + using Traits = ArrayTraits; + + static_assert(sizeof(Element) == sizeof(DataElement), + "Incorrect array serializer"); + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + return sizeof(Data) + Align(input->GetSize() * sizeof(DataElement)); + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + DCHECK(!validate_params->element_is_nullable) + << "Primitive type should be non-nullable"; + DCHECK(!validate_params->element_validate_params) + << "Primitive type should not have array validate params"; + + size_t size = input->GetSize(); + for (size_t i = 0; i < size; ++i) + Serialize(input->GetNext(), output->storage() + i); + } + + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + if (!Deserialize(input->at(i), &iterator.GetNext())) + return false; + } + return true; + } +}; + +// Serializes and deserializes arrays of bools. +template +struct ArraySerializer::value>::type> { + using UserType = typename std::remove_const::type; + using Traits = ArrayTraits; + using Data = typename MojomTypeTraits::Data; + + static_assert(std::is_same::value, + "Incorrect array serializer"); + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + return sizeof(Data) + Align((input->GetSize() + 7) / 8); + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + DCHECK(!validate_params->element_is_nullable) + << "Primitive type should be non-nullable"; + DCHECK(!validate_params->element_validate_params) + << "Primitive type should not have array validate params"; + + size_t size = input->GetSize(); + for (size_t i = 0; i < size; ++i) + output->at(i) = input->GetNext(); + } + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator iterator(*output); + for (size_t i = 0; i < input->size(); ++i) + iterator.GetNext() = input->at(i); + return true; + } +}; + +// Serializes and deserializes arrays of handles or interfaces. +template +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if< + BelongsTo::value>::type> { + using UserType = typename std::remove_const::type; + using Data = typename MojomTypeTraits::Data; + using Element = typename MojomType::Element; + using Traits = ArrayTraits; + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + size_t element_count = input->GetSize(); + if (BelongsTo::value) { + for (size_t i = 0; i < element_count; ++i) { + typename UserTypeIterator::GetNextResult next = input->GetNext(); + size_t size = PrepareToSerialize(next, context); + DCHECK_EQ(size, 0u); + } + } + return sizeof(Data) + Align(element_count * sizeof(typename Data::Element)); + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + DCHECK(!validate_params->element_validate_params) + << "Handle or interface type should not have array validate params"; + + size_t size = input->GetSize(); + for (size_t i = 0; i < size; ++i) { + typename UserTypeIterator::GetNextResult next = input->GetNext(); + Serialize(next, &output->at(i), context); + + static const ValidationError kError = + BelongsTo::value + ? VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID + : VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE; + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !validate_params->element_is_nullable && + !IsHandleOrInterfaceValid(output->at(i)), + kError, + MakeMessageWithArrayIndex("invalid handle or interface ID in array " + "expecting valid handles or interface IDs", + size, i)); + } + } + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + bool result = + Deserialize(&input->at(i), &iterator.GetNext(), context); + DCHECK(result); + } + return true; + } +}; + +// This template must only apply to pointer mojo entity (strings, structs, +// arrays and maps). +template +struct ArraySerializer::value>::type> { + using UserType = typename std::remove_const::type; + using Data = typename MojomTypeTraits::Data; + using Element = typename MojomType::Element; + using DataElementPtr = typename MojomTypeTraits::Data*; + using Traits = ArrayTraits; + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + size_t element_count = input->GetSize(); + size_t size = sizeof(Data) + element_count * sizeof(typename Data::Element); + for (size_t i = 0; i < element_count; ++i) { + typename UserTypeIterator::GetNextResult next = input->GetNext(); + size += PrepareToSerialize(next, context); + } + return size; + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + size_t size = input->GetSize(); + for (size_t i = 0; i < size; ++i) { + DataElementPtr data_ptr; + typename UserTypeIterator::GetNextResult next = input->GetNext(); + SerializeCaller::Run(next, buf, &data_ptr, + validate_params->element_validate_params, + context); + output->at(i).Set(data_ptr); + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !validate_params->element_is_nullable && !data_ptr, + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid pointers", + size, i)); + } + } + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + if (!Deserialize(input->at(i).Get(), &iterator.GetNext(), + context)) + return false; + } + return true; + } + + private: + template ::value> + struct SerializeCaller { + template + static void Run(InputElementType&& input, + Buffer* buf, + DataElementPtr* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + Serialize(std::forward(input), buf, output, context); + } + }; + + template + struct SerializeCaller { + template + static void Run(InputElementType&& input, + Buffer* buf, + DataElementPtr* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + Serialize(std::forward(input), buf, output, + validate_params, context); + } + }; +}; + +// Handles serialization and deserialization of arrays of unions. +template +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if::value>::type> { + using UserType = typename std::remove_const::type; + using Data = typename MojomTypeTraits::Data; + using Element = typename MojomType::Element; + using Traits = ArrayTraits; + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + size_t element_count = input->GetSize(); + size_t size = sizeof(Data); + for (size_t i = 0; i < element_count; ++i) { + // Call with |inlined| set to false, so that it will account for both the + // data in the union and the space in the array used to hold the union. + typename UserTypeIterator::GetNextResult next = input->GetNext(); + size += PrepareToSerialize(next, false, context); + } + return size; + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + size_t size = input->GetSize(); + for (size_t i = 0; i < size; ++i) { + typename Data::Element* result = output->storage() + i; + typename UserTypeIterator::GetNextResult next = input->GetNext(); + Serialize(next, buf, &result, true, context); + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !validate_params->element_is_nullable && output->at(i).is_null(), + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid unions", + size, i)); + } + } + + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + if (!Deserialize(&input->at(i), &iterator.GetNext(), context)) + return false; + } + return true; + } +}; + +template +struct Serializer, MaybeConstUserType> { + using UserType = typename std::remove_const::type; + using Traits = ArrayTraits; + using Impl = ArraySerializer, + MaybeConstUserType, + ArrayIterator>; + using Data = typename MojomTypeTraits>::Data; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + SerializationContext* context) { + if (CallIsNullIfExists(input)) + return 0; + ArrayIterator iterator(input); + return Impl::GetSerializedSize(&iterator, context); + } + + static void Serialize(MaybeConstUserType& input, + Buffer* buf, + Data** output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + if (!CallIsNullIfExists(input)) { + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + validate_params->expected_num_elements != 0 && + Traits::GetSize(input) != validate_params->expected_num_elements, + internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + internal::MakeMessageWithExpectedArraySize( + "fixed-size array has wrong number of elements", + Traits::GetSize(input), validate_params->expected_num_elements)); + Data* result = Data::New(Traits::GetSize(input), buf); + if (result) { + ArrayIterator iterator(input); + Impl::SerializeElements(&iterator, buf, result, validate_params, + context); + } + *output = result; + } else { + *output = nullptr; + } + } + + static bool Deserialize(Data* input, + UserType* output, + SerializationContext* context) { + if (!input) + return CallSetToNullIfExists(output); + return Impl::DeserializeElements(input, output, context); + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/associated_binding.cc b/mojo/public/cpp/bindings/lib/associated_binding.cc new file mode 100644 index 0000000000000000000000000000000000000000..6788e68e071b3b893e674d5f67e58c1066b2a079 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/associated_binding.cc @@ -0,0 +1,62 @@ +// 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. + +#include "mojo/public/cpp/bindings/associated_binding.h" + +namespace mojo { + +AssociatedBindingBase::AssociatedBindingBase() {} + +AssociatedBindingBase::~AssociatedBindingBase() {} + +void AssociatedBindingBase::AddFilter(std::unique_ptr filter) { + DCHECK(endpoint_client_); + endpoint_client_->AddFilter(std::move(filter)); +} + +void AssociatedBindingBase::Close() { + endpoint_client_.reset(); +} + +void AssociatedBindingBase::CloseWithReason(uint32_t custom_reason, + const std::string& description) { + if (endpoint_client_) + endpoint_client_->CloseWithReason(custom_reason, description); + Close(); +} + +void AssociatedBindingBase::set_connection_error_handler( + const base::Closure& error_handler) { + DCHECK(is_bound()); + endpoint_client_->set_connection_error_handler(error_handler); +} + +void AssociatedBindingBase::set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(is_bound()); + endpoint_client_->set_connection_error_with_reason_handler(error_handler); +} + +void AssociatedBindingBase::FlushForTesting() { + endpoint_client_->FlushForTesting(); +} + +void AssociatedBindingBase::BindImpl( + ScopedInterfaceEndpointHandle handle, + MessageReceiverWithResponderStatus* receiver, + std::unique_ptr payload_validator, + bool expect_sync_requests, + scoped_refptr runner, + uint32_t interface_version) { + if (!handle.is_valid()) { + endpoint_client_.reset(); + return; + } + + endpoint_client_.reset(new InterfaceEndpointClient( + std::move(handle), receiver, std::move(payload_validator), + expect_sync_requests, std::move(runner), interface_version)); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/associated_group.cc b/mojo/public/cpp/bindings/lib/associated_group.cc new file mode 100644 index 0000000000000000000000000000000000000000..3e95eeb0276807de1a3c2469fbaca8826def3324 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/associated_group.cc @@ -0,0 +1,34 @@ +// 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. + +#include "mojo/public/cpp/bindings/associated_group.h" + +#include "mojo/public/cpp/bindings/associated_group_controller.h" + +namespace mojo { + +AssociatedGroup::AssociatedGroup() = default; + +AssociatedGroup::AssociatedGroup( + scoped_refptr controller) + : controller_(std::move(controller)) {} + +AssociatedGroup::AssociatedGroup(const ScopedInterfaceEndpointHandle& handle) + : controller_getter_(handle.CreateGroupControllerGetter()) {} + +AssociatedGroup::AssociatedGroup(const AssociatedGroup& other) = default; + +AssociatedGroup::~AssociatedGroup() = default; + +AssociatedGroup& AssociatedGroup::operator=(const AssociatedGroup& other) = + default; + +AssociatedGroupController* AssociatedGroup::GetController() { + if (controller_) + return controller_.get(); + + return controller_getter_.Run(); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/associated_group_controller.cc b/mojo/public/cpp/bindings/lib/associated_group_controller.cc new file mode 100644 index 0000000000000000000000000000000000000000..f4a9aa2852aa27d55c90bfa322d9d888faa1f0bd --- /dev/null +++ b/mojo/public/cpp/bindings/lib/associated_group_controller.cc @@ -0,0 +1,24 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/associated_group_controller.h" + +#include "mojo/public/cpp/bindings/associated_group.h" + +namespace mojo { + +AssociatedGroupController::~AssociatedGroupController() {} + +ScopedInterfaceEndpointHandle +AssociatedGroupController::CreateScopedInterfaceEndpointHandle(InterfaceId id) { + return ScopedInterfaceEndpointHandle(id, this); +} + +bool AssociatedGroupController::NotifyAssociation( + ScopedInterfaceEndpointHandle* handle_to_send, + InterfaceId id) { + return handle_to_send->NotifyAssociation(id, this); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc new file mode 100644 index 0000000000000000000000000000000000000000..78281eda9a9eb4048f4c610ebadd23977c3ea675 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc @@ -0,0 +1,18 @@ +// 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. + +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" + +namespace mojo { + +void GetIsolatedInterface(ScopedInterfaceEndpointHandle handle) { + MessagePipe pipe; + scoped_refptr router = + new internal::MultiplexRouter(std::move(pipe.handle0), + internal::MultiplexRouter::MULTI_INTERFACE, + false, base::ThreadTaskRunnerHandle::Get()); + router->AssociateInterface(std::move(handle)); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h new file mode 100644 index 0000000000000000000000000000000000000000..a4b51882d202ce2f94464d190af4e43e2e28f9c8 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h @@ -0,0 +1,157 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ASSOCIATED_INTERFACE_PTR_STATE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ASSOCIATED_INTERFACE_PTR_STATE_H_ + +#include + +#include // For |std::swap()|. +#include +#include +#include + +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "mojo/public/cpp/bindings/associated_group.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace mojo { +namespace internal { + +template +class AssociatedInterfacePtrState { + public: + AssociatedInterfacePtrState() : version_(0u) {} + + ~AssociatedInterfacePtrState() { + endpoint_client_.reset(); + proxy_.reset(); + } + + Interface* instance() { + // This will be null if the object is not bound. + return proxy_.get(); + } + + uint32_t version() const { return version_; } + + void QueryVersion(const base::Callback& callback) { + // It is safe to capture |this| because the callback won't be run after this + // object goes away. + endpoint_client_->QueryVersion( + base::Bind(&AssociatedInterfacePtrState::OnQueryVersion, + base::Unretained(this), callback)); + } + + void RequireVersion(uint32_t version) { + if (version <= version_) + return; + + version_ = version; + endpoint_client_->RequireVersion(version); + } + + void FlushForTesting() { endpoint_client_->FlushForTesting(); } + + void CloseWithReason(uint32_t custom_reason, const std::string& description) { + endpoint_client_->CloseWithReason(custom_reason, description); + } + + void Swap(AssociatedInterfacePtrState* other) { + using std::swap; + swap(other->endpoint_client_, endpoint_client_); + swap(other->proxy_, proxy_); + swap(other->version_, version_); + } + + void Bind(AssociatedInterfacePtrInfo info, + scoped_refptr runner) { + DCHECK(!endpoint_client_); + DCHECK(!proxy_); + DCHECK_EQ(0u, version_); + DCHECK(info.is_valid()); + + version_ = info.version(); + // The version is only queried from the client so the value passed here + // will not be used. + endpoint_client_.reset(new InterfaceEndpointClient( + info.PassHandle(), nullptr, + base::WrapUnique(new typename Interface::ResponseValidator_()), false, + std::move(runner), 0u)); + proxy_.reset(new Proxy(endpoint_client_.get())); + } + + // After this method is called, the object is in an invalid state and + // shouldn't be reused. + AssociatedInterfacePtrInfo PassInterface() { + ScopedInterfaceEndpointHandle handle = endpoint_client_->PassHandle(); + endpoint_client_.reset(); + proxy_.reset(); + return AssociatedInterfacePtrInfo(std::move(handle), version_); + } + + bool is_bound() const { return !!endpoint_client_; } + + bool encountered_error() const { + return endpoint_client_ ? endpoint_client_->encountered_error() : false; + } + + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(endpoint_client_); + endpoint_client_->set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(endpoint_client_); + endpoint_client_->set_connection_error_with_reason_handler(error_handler); + } + + // Returns true if bound and awaiting a response to a message. + bool has_pending_callbacks() const { + return endpoint_client_ && endpoint_client_->has_pending_responders(); + } + + AssociatedGroup* associated_group() { + return endpoint_client_ ? endpoint_client_->associated_group() : nullptr; + } + + void ForwardMessage(Message message) { endpoint_client_->Accept(&message); } + + void ForwardMessageWithResponder(Message message, + std::unique_ptr responder) { + endpoint_client_->AcceptWithResponder(&message, std::move(responder)); + } + + private: + using Proxy = typename Interface::Proxy_; + + void OnQueryVersion(const base::Callback& callback, + uint32_t version) { + version_ = version; + callback.Run(version); + } + + std::unique_ptr endpoint_client_; + std::unique_ptr proxy_; + + uint32_t version_; + + DISALLOW_COPY_AND_ASSIGN(AssociatedInterfacePtrState); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ASSOCIATED_INTERFACE_PTR_STATE_H_ diff --git a/mojo/public/cpp/bindings/lib/binding_state.cc b/mojo/public/cpp/bindings/lib/binding_state.cc new file mode 100644 index 0000000000000000000000000000000000000000..b34cb47e289c754f229879ca5f8016b5bcedfd32 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/binding_state.cc @@ -0,0 +1,90 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/lib/binding_state.h" + +namespace mojo { +namespace internal { + +BindingStateBase::BindingStateBase() = default; + +BindingStateBase::~BindingStateBase() = default; + +void BindingStateBase::AddFilter(std::unique_ptr filter) { + DCHECK(endpoint_client_); + endpoint_client_->AddFilter(std::move(filter)); +} + +bool BindingStateBase::HasAssociatedInterfaces() const { + return router_ ? router_->HasAssociatedEndpoints() : false; +} + +void BindingStateBase::PauseIncomingMethodCallProcessing() { + DCHECK(router_); + router_->PauseIncomingMethodCallProcessing(); +} +void BindingStateBase::ResumeIncomingMethodCallProcessing() { + DCHECK(router_); + router_->ResumeIncomingMethodCallProcessing(); +} + +bool BindingStateBase::WaitForIncomingMethodCall(MojoDeadline deadline) { + DCHECK(router_); + return router_->WaitForIncomingMessage(deadline); +} + +void BindingStateBase::Close() { + if (!router_) + return; + + endpoint_client_.reset(); + router_->CloseMessagePipe(); + router_ = nullptr; +} + +void BindingStateBase::CloseWithReason(uint32_t custom_reason, + const std::string& description) { + if (endpoint_client_) + endpoint_client_->CloseWithReason(custom_reason, description); + + Close(); +} + +void BindingStateBase::FlushForTesting() { + endpoint_client_->FlushForTesting(); +} + +void BindingStateBase::EnableTestingMode() { + DCHECK(is_bound()); + router_->EnableTestingMode(); +} + +void BindingStateBase::BindInternal( + ScopedMessagePipeHandle handle, + scoped_refptr runner, + const char* interface_name, + std::unique_ptr request_validator, + bool passes_associated_kinds, + bool has_sync_methods, + MessageReceiverWithResponderStatus* stub, + uint32_t interface_version) { + DCHECK(!router_); + + MultiplexRouter::Config config = + passes_associated_kinds + ? MultiplexRouter::MULTI_INTERFACE + : (has_sync_methods + ? MultiplexRouter::SINGLE_INTERFACE_WITH_SYNC_METHODS + : MultiplexRouter::SINGLE_INTERFACE); + router_ = new MultiplexRouter(std::move(handle), config, false, runner); + router_->SetMasterInterfaceName(interface_name); + + endpoint_client_.reset(new InterfaceEndpointClient( + router_->CreateLocalEndpointHandle(kMasterInterfaceId), stub, + std::move(request_validator), has_sync_methods, std::move(runner), + interface_version)); +} + +} // namesapce internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/binding_state.h b/mojo/public/cpp/bindings/lib/binding_state.h new file mode 100644 index 0000000000000000000000000000000000000000..0b0dbee0026d07be01c722702eb8da9eacf1ea44 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/binding_state.h @@ -0,0 +1,128 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDING_STATE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDING_STATE_H_ + +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/filter_chain.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" +#include "mojo/public/cpp/bindings/message_header_validator.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace internal { + +class MOJO_CPP_BINDINGS_EXPORT BindingStateBase { + public: + BindingStateBase(); + ~BindingStateBase(); + + void AddFilter(std::unique_ptr filter); + + bool HasAssociatedInterfaces() const; + + void PauseIncomingMethodCallProcessing(); + void ResumeIncomingMethodCallProcessing(); + + bool WaitForIncomingMethodCall( + MojoDeadline deadline = MOJO_DEADLINE_INDEFINITE); + + void Close(); + void CloseWithReason(uint32_t custom_reason, const std::string& description); + + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(is_bound()); + endpoint_client_->set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(is_bound()); + endpoint_client_->set_connection_error_with_reason_handler(error_handler); + } + + bool is_bound() const { return !!router_; } + + MessagePipeHandle handle() const { + DCHECK(is_bound()); + return router_->handle(); + } + + void FlushForTesting(); + + void EnableTestingMode(); + + protected: + void BindInternal(ScopedMessagePipeHandle handle, + scoped_refptr runner, + const char* interface_name, + std::unique_ptr request_validator, + bool passes_associated_kinds, + bool has_sync_methods, + MessageReceiverWithResponderStatus* stub, + uint32_t interface_version); + + scoped_refptr router_; + std::unique_ptr endpoint_client_; +}; + +template +class BindingState : public BindingStateBase { + public: + using ImplPointerType = typename ImplRefTraits::PointerType; + + explicit BindingState(ImplPointerType impl) { + stub_.set_sink(std::move(impl)); + } + + ~BindingState() { Close(); } + + void Bind(ScopedMessagePipeHandle handle, + scoped_refptr runner) { + BindingStateBase::BindInternal( + std::move(handle), runner, Interface::Name_, + base::MakeUnique(), + Interface::PassesAssociatedKinds_, Interface::HasSyncMethods_, &stub_, + Interface::Version_); + } + + InterfaceRequest Unbind() { + endpoint_client_.reset(); + InterfaceRequest request = + MakeRequest(router_->PassMessagePipe()); + router_ = nullptr; + return request; + } + + Interface* impl() { return ImplRefTraits::GetRawPointer(&stub_.sink()); } + + private: + typename Interface::template Stub_ stub_; + + DISALLOW_COPY_AND_ASSIGN(BindingState); +}; + +} // namesapce internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDING_STATE_H_ diff --git a/mojo/public/cpp/bindings/lib/bindings_internal.h b/mojo/public/cpp/bindings/lib/bindings_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..631daec392e1b9e558a87a198265af474cf953a0 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/bindings_internal.h @@ -0,0 +1,336 @@ +// Copyright 2013 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ + +#include + +#include + +#include "base/template_util.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +template +class ArrayDataView; + +template +class AssociatedInterfacePtrInfoDataView; + +template +class AssociatedInterfaceRequestDataView; + +template +class InterfacePtrDataView; + +template +class InterfaceRequestDataView; + +template +class MapDataView; + +class NativeStructDataView; + +class StringDataView; + +namespace internal { + +// Please note that this is a different value than |mojo::kInvalidHandleValue|, +// which is the "decoded" invalid handle. +const uint32_t kEncodedInvalidHandleValue = static_cast(-1); + +// A serialized union always takes 16 bytes: +// 4-byte size + 4-byte tag + 8-byte payload. +const uint32_t kUnionDataSize = 16; + +template +class Array_Data; + +template +class Map_Data; + +class NativeStruct_Data; + +using String_Data = Array_Data; + +inline size_t Align(size_t size) { + return (size + 7) & ~0x7; +} + +inline bool IsAligned(const void* ptr) { + return !(reinterpret_cast(ptr) & 0x7); +} + +// Pointers are encoded as relative offsets. The offsets are relative to the +// address of where the offset value is stored, such that the pointer may be +// recovered with the expression: +// +// ptr = reinterpret_cast(offset) + *offset +// +// A null pointer is encoded as an offset value of 0. +// +inline void EncodePointer(const void* ptr, uint64_t* offset) { + if (!ptr) { + *offset = 0; + return; + } + + const char* p_obj = reinterpret_cast(ptr); + const char* p_slot = reinterpret_cast(offset); + DCHECK(p_obj > p_slot); + + *offset = static_cast(p_obj - p_slot); +} + +// Note: This function doesn't validate the encoded pointer value. +inline const void* DecodePointer(const uint64_t* offset) { + if (!*offset) + return nullptr; + return reinterpret_cast(offset) + *offset; +} + +#pragma pack(push, 1) + +struct StructHeader { + uint32_t num_bytes; + uint32_t version; +}; +static_assert(sizeof(StructHeader) == 8, "Bad sizeof(StructHeader)"); + +struct ArrayHeader { + uint32_t num_bytes; + uint32_t num_elements; +}; +static_assert(sizeof(ArrayHeader) == 8, "Bad_sizeof(ArrayHeader)"); + +template +struct Pointer { + using BaseType = T; + + void Set(T* ptr) { EncodePointer(ptr, &offset); } + const T* Get() const { return static_cast(DecodePointer(&offset)); } + T* Get() { + return static_cast(const_cast(DecodePointer(&offset))); + } + + bool is_null() const { return offset == 0; } + + uint64_t offset; +}; +static_assert(sizeof(Pointer) == 8, "Bad_sizeof(Pointer)"); + +using GenericPointer = Pointer; + +struct Handle_Data { + Handle_Data() = default; + explicit Handle_Data(uint32_t value) : value(value) {} + + bool is_valid() const { return value != kEncodedInvalidHandleValue; } + + uint32_t value; +}; +static_assert(sizeof(Handle_Data) == 4, "Bad_sizeof(Handle_Data)"); + +struct Interface_Data { + Handle_Data handle; + uint32_t version; +}; +static_assert(sizeof(Interface_Data) == 8, "Bad_sizeof(Interface_Data)"); + +struct AssociatedEndpointHandle_Data { + AssociatedEndpointHandle_Data() = default; + explicit AssociatedEndpointHandle_Data(uint32_t value) : value(value) {} + + bool is_valid() const { return value != kEncodedInvalidHandleValue; } + + uint32_t value; +}; +static_assert(sizeof(AssociatedEndpointHandle_Data) == 4, + "Bad_sizeof(AssociatedEndpointHandle_Data)"); + +struct AssociatedInterface_Data { + AssociatedEndpointHandle_Data handle; + uint32_t version; +}; +static_assert(sizeof(AssociatedInterface_Data) == 8, + "Bad_sizeof(AssociatedInterface_Data)"); + +#pragma pack(pop) + +template +T FetchAndReset(T* ptr) { + T temp = *ptr; + *ptr = T(); + return temp; +} + +template +struct IsUnionDataType { + private: + template + static YesType Test(const typename U::MojomUnionDataType*); + + template + static NoType Test(...); + + EnsureTypeIsComplete check_t_; + + public: + static const bool value = + sizeof(Test(0)) == sizeof(YesType) && !IsConst::value; +}; + +enum class MojomTypeCategory : uint32_t { + ARRAY = 1 << 0, + ASSOCIATED_INTERFACE = 1 << 1, + ASSOCIATED_INTERFACE_REQUEST = 1 << 2, + BOOLEAN = 1 << 3, + ENUM = 1 << 4, + HANDLE = 1 << 5, + INTERFACE = 1 << 6, + INTERFACE_REQUEST = 1 << 7, + MAP = 1 << 8, + // POD except boolean and enum. + POD = 1 << 9, + STRING = 1 << 10, + STRUCT = 1 << 11, + UNION = 1 << 12 +}; + +inline constexpr MojomTypeCategory operator&(MojomTypeCategory x, + MojomTypeCategory y) { + return static_cast(static_cast(x) & + static_cast(y)); +} + +inline constexpr MojomTypeCategory operator|(MojomTypeCategory x, + MojomTypeCategory y) { + return static_cast(static_cast(x) | + static_cast(y)); +} + +template ::value> +struct MojomTypeTraits { + using Data = T; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::POD; +}; + +template +struct MojomTypeTraits, false> { + using Data = Array_Data::DataAsArrayElement>; + using DataAsArrayElement = Pointer; + + static const MojomTypeCategory category = MojomTypeCategory::ARRAY; +}; + +template +struct MojomTypeTraits, false> { + using Data = AssociatedInterface_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = + MojomTypeCategory::ASSOCIATED_INTERFACE; +}; + +template +struct MojomTypeTraits, false> { + using Data = AssociatedEndpointHandle_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = + MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST; +}; + +template <> +struct MojomTypeTraits { + using Data = bool; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::BOOLEAN; +}; + +template +struct MojomTypeTraits { + using Data = int32_t; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::ENUM; +}; + +template +struct MojomTypeTraits, false> { + using Data = Handle_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::HANDLE; +}; + +template +struct MojomTypeTraits, false> { + using Data = Interface_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::INTERFACE; +}; + +template +struct MojomTypeTraits, false> { + using Data = Handle_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = + MojomTypeCategory::INTERFACE_REQUEST; +}; + +template +struct MojomTypeTraits, false> { + using Data = Map_Data::DataAsArrayElement, + typename MojomTypeTraits::DataAsArrayElement>; + using DataAsArrayElement = Pointer; + + static const MojomTypeCategory category = MojomTypeCategory::MAP; +}; + +template <> +struct MojomTypeTraits { + using Data = internal::NativeStruct_Data; + using DataAsArrayElement = Pointer; + + static const MojomTypeCategory category = MojomTypeCategory::STRUCT; +}; + +template <> +struct MojomTypeTraits { + using Data = String_Data; + using DataAsArrayElement = Pointer; + + static const MojomTypeCategory category = MojomTypeCategory::STRING; +}; + +template +struct BelongsTo { + static const bool value = + static_cast(MojomTypeTraits::category & categories) != 0; +}; + +template +struct EnumHashImpl { + static_assert(std::is_enum::value, "Incorrect hash function."); + + size_t operator()(T input) const { + using UnderlyingType = typename base::underlying_type::type; + return std::hash()(static_cast(input)); + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/buffer.h b/mojo/public/cpp/bindings/lib/buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..213a44590f60838403f31f534fa83bc3c45682e5 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/buffer.h @@ -0,0 +1,70 @@ +// Copyright 2013 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ + +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" + +namespace mojo { +namespace internal { + +// Buffer provides an interface to allocate memory blocks which are 8-byte +// aligned and zero-initialized. It doesn't own the underlying memory. Users +// must ensure that the memory stays valid while using the allocated blocks from +// Buffer. +class Buffer { + public: + Buffer() {} + + // The memory must have been zero-initialized. |data| must be 8-byte + // aligned. + void Initialize(void* data, size_t size) { + DCHECK(IsAligned(data)); + + data_ = data; + size_ = size; + cursor_ = reinterpret_cast(data); + data_end_ = cursor_ + size; + } + + size_t size() const { return size_; } + + void* data() const { return data_; } + + // Allocates |num_bytes| from the buffer and returns a pointer to the start of + // the allocated block. + // The resulting address is 8-byte aligned, and the content of the memory is + // zero-filled. + void* Allocate(size_t num_bytes) { + num_bytes = Align(num_bytes); + uintptr_t result = cursor_; + cursor_ += num_bytes; + if (cursor_ > data_end_ || cursor_ < result) { + NOTREACHED(); + cursor_ -= num_bytes; + return nullptr; + } + + return reinterpret_cast(result); + } + + private: + void* data_ = nullptr; + size_t size_ = 0; + + uintptr_t cursor_ = 0; + uintptr_t data_end_ = 0; + + DISALLOW_COPY_AND_ASSIGN(Buffer); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ diff --git a/mojo/public/cpp/bindings/lib/connector.cc b/mojo/public/cpp/bindings/lib/connector.cc new file mode 100644 index 0000000000000000000000000000000000000000..d93e45ed938cd9455222aed5855af75998408ab7 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/connector.cc @@ -0,0 +1,493 @@ +// Copyright 2013 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. + +#include "mojo/public/cpp/bindings/connector.h" + +#include +#include + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_local.h" +#include "mojo/public/cpp/bindings/lib/may_auto_lock.h" +#include "mojo/public/cpp/bindings/sync_handle_watcher.h" +#include "mojo/public/cpp/system/wait.h" + +namespace mojo { + +namespace { + +// The NestingObserver for each thread. Note that this is always a +// Connector::MessageLoopNestingObserver; we use the base type here because that +// subclass is private to Connector. +base::LazyInstance< + base::ThreadLocalPointer>::Leaky + g_tls_nesting_observer = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// Used to efficiently maintain a doubly-linked list of all Connectors +// currently dispatching on any given thread. +class Connector::ActiveDispatchTracker { + public: + explicit ActiveDispatchTracker(const base::WeakPtr& connector); + ~ActiveDispatchTracker(); + + void NotifyBeginNesting(); + + private: + const base::WeakPtr connector_; + MessageLoopNestingObserver* const nesting_observer_; + ActiveDispatchTracker* outer_tracker_ = nullptr; + ActiveDispatchTracker* inner_tracker_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(ActiveDispatchTracker); +}; + +// Watches the MessageLoop on the current thread. Notifies the current chain of +// ActiveDispatchTrackers when a nested message loop is started. +class Connector::MessageLoopNestingObserver + : public base::MessageLoop::NestingObserver, + public base::MessageLoop::DestructionObserver { + public: + MessageLoopNestingObserver() { + base::MessageLoop::current()->AddNestingObserver(this); + base::MessageLoop::current()->AddDestructionObserver(this); + } + + ~MessageLoopNestingObserver() override {} + + // base::MessageLoop::NestingObserver: + void OnBeginNestedMessageLoop() override { + if (top_tracker_) + top_tracker_->NotifyBeginNesting(); + } + + // base::MessageLoop::DestructionObserver: + void WillDestroyCurrentMessageLoop() override { + base::MessageLoop::current()->RemoveNestingObserver(this); + base::MessageLoop::current()->RemoveDestructionObserver(this); + DCHECK_EQ(this, g_tls_nesting_observer.Get().Get()); + g_tls_nesting_observer.Get().Set(nullptr); + delete this; + } + + static MessageLoopNestingObserver* GetForThread() { + if (!base::MessageLoop::current() || + !base::MessageLoop::current()->nesting_allowed()) + return nullptr; + auto* observer = static_cast( + g_tls_nesting_observer.Get().Get()); + if (!observer) { + observer = new MessageLoopNestingObserver; + g_tls_nesting_observer.Get().Set(observer); + } + return observer; + } + + private: + friend class ActiveDispatchTracker; + + ActiveDispatchTracker* top_tracker_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(MessageLoopNestingObserver); +}; + +Connector::ActiveDispatchTracker::ActiveDispatchTracker( + const base::WeakPtr& connector) + : connector_(connector), nesting_observer_(connector_->nesting_observer_) { + DCHECK(nesting_observer_); + if (nesting_observer_->top_tracker_) { + outer_tracker_ = nesting_observer_->top_tracker_; + outer_tracker_->inner_tracker_ = this; + } + nesting_observer_->top_tracker_ = this; +} + +Connector::ActiveDispatchTracker::~ActiveDispatchTracker() { + if (nesting_observer_->top_tracker_ == this) + nesting_observer_->top_tracker_ = outer_tracker_; + else if (inner_tracker_) + inner_tracker_->outer_tracker_ = outer_tracker_; + if (outer_tracker_) + outer_tracker_->inner_tracker_ = inner_tracker_; +} + +void Connector::ActiveDispatchTracker::NotifyBeginNesting() { + if (connector_ && connector_->handle_watcher_) + connector_->handle_watcher_->ArmOrNotify(); + if (outer_tracker_) + outer_tracker_->NotifyBeginNesting(); +} + +Connector::Connector(ScopedMessagePipeHandle message_pipe, + ConnectorConfig config, + scoped_refptr runner) + : message_pipe_(std::move(message_pipe)), + task_runner_(std::move(runner)), + nesting_observer_(MessageLoopNestingObserver::GetForThread()), + weak_factory_(this) { + if (config == MULTI_THREADED_SEND) + lock_.emplace(); + + weak_self_ = weak_factory_.GetWeakPtr(); + // Even though we don't have an incoming receiver, we still want to monitor + // the message pipe to know if is closed or encounters an error. + WaitToReadMore(); +} + +Connector::~Connector() { + { + // Allow for quick destruction on any thread if the pipe is already closed. + base::AutoLock lock(connected_lock_); + if (!connected_) + return; + } + + DCHECK(thread_checker_.CalledOnValidThread()); + CancelWait(); +} + +void Connector::CloseMessagePipe() { + // Throw away the returned message pipe. + PassMessagePipe(); +} + +ScopedMessagePipeHandle Connector::PassMessagePipe() { + DCHECK(thread_checker_.CalledOnValidThread()); + + CancelWait(); + internal::MayAutoLock locker(&lock_); + ScopedMessagePipeHandle message_pipe = std::move(message_pipe_); + weak_factory_.InvalidateWeakPtrs(); + sync_handle_watcher_callback_count_ = 0; + + base::AutoLock lock(connected_lock_); + connected_ = false; + return message_pipe; +} + +void Connector::RaiseError() { + DCHECK(thread_checker_.CalledOnValidThread()); + + HandleError(true, true); +} + +bool Connector::WaitForIncomingMessage(MojoDeadline deadline) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (error_) + return false; + + ResumeIncomingMethodCallProcessing(); + + // TODO(rockot): Use a timed Wait here. Nobody uses anything but 0 or + // INDEFINITE deadlines at present, so we only support those. + DCHECK(deadline == 0 || deadline == MOJO_DEADLINE_INDEFINITE); + + MojoResult rv = MOJO_RESULT_UNKNOWN; + if (deadline == 0 && !message_pipe_->QuerySignalsState().readable()) + return false; + + if (deadline == MOJO_DEADLINE_INDEFINITE) { + rv = Wait(message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE); + if (rv != MOJO_RESULT_OK) { + // Users that call WaitForIncomingMessage() should expect their code to be + // re-entered, so we call the error handler synchronously. + HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false); + return false; + } + } + + ignore_result(ReadSingleMessage(&rv)); + return (rv == MOJO_RESULT_OK); +} + +void Connector::PauseIncomingMethodCallProcessing() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (paused_) + return; + + paused_ = true; + CancelWait(); +} + +void Connector::ResumeIncomingMethodCallProcessing() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!paused_) + return; + + paused_ = false; + WaitToReadMore(); +} + +bool Connector::Accept(Message* message) { + DCHECK(lock_ || thread_checker_.CalledOnValidThread()); + + // It shouldn't hurt even if |error_| may be changed by a different thread at + // the same time. The outcome is that we may write into |message_pipe_| after + // encountering an error, which should be fine. + if (error_) + return false; + + internal::MayAutoLock locker(&lock_); + + if (!message_pipe_.is_valid() || drop_writes_) + return true; + + MojoResult rv = + WriteMessageNew(message_pipe_.get(), message->TakeMojoMessage(), + MOJO_WRITE_MESSAGE_FLAG_NONE); + + switch (rv) { + case MOJO_RESULT_OK: + break; + case MOJO_RESULT_FAILED_PRECONDITION: + // There's no point in continuing to write to this pipe since the other + // end is gone. Avoid writing any future messages. Hide write failures + // from the caller since we'd like them to continue consuming any backlog + // of incoming messages before regarding the message pipe as closed. + drop_writes_ = true; + break; + case MOJO_RESULT_BUSY: + // We'd get a "busy" result if one of the message's handles is: + // - |message_pipe_|'s own handle; + // - simultaneously being used on another thread; or + // - in a "busy" state that prohibits it from being transferred (e.g., + // a data pipe handle in the middle of a two-phase read/write, + // regardless of which thread that two-phase read/write is happening + // on). + // TODO(vtl): I wonder if this should be a |DCHECK()|. (But, until + // crbug.com/389666, etc. are resolved, this will make tests fail quickly + // rather than hanging.) + CHECK(false) << "Race condition or other bug detected"; + return false; + default: + // This particular write was rejected, presumably because of bad input. + // The pipe is not necessarily in a bad state. + return false; + } + return true; +} + +void Connector::AllowWokenUpBySyncWatchOnSameThread() { + DCHECK(thread_checker_.CalledOnValidThread()); + + allow_woken_up_by_others_ = true; + + EnsureSyncWatcherExists(); + sync_watcher_->AllowWokenUpBySyncWatchOnSameThread(); +} + +bool Connector::SyncWatch(const bool* should_stop) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (error_) + return false; + + ResumeIncomingMethodCallProcessing(); + + EnsureSyncWatcherExists(); + return sync_watcher_->SyncWatch(should_stop); +} + +void Connector::SetWatcherHeapProfilerTag(const char* tag) { + heap_profiler_tag_ = tag; + if (handle_watcher_) { + handle_watcher_->set_heap_profiler_tag(tag); + } +} + +void Connector::OnWatcherHandleReady(MojoResult result) { + OnHandleReadyInternal(result); +} + +void Connector::OnSyncHandleWatcherHandleReady(MojoResult result) { + base::WeakPtr weak_self(weak_self_); + + sync_handle_watcher_callback_count_++; + OnHandleReadyInternal(result); + // At this point, this object might have been deleted. + if (weak_self) { + DCHECK_LT(0u, sync_handle_watcher_callback_count_); + sync_handle_watcher_callback_count_--; + } +} + +void Connector::OnHandleReadyInternal(MojoResult result) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (result != MOJO_RESULT_OK) { + HandleError(result != MOJO_RESULT_FAILED_PRECONDITION, false); + return; + } + + ReadAllAvailableMessages(); + // At this point, this object might have been deleted. Return. +} + +void Connector::WaitToReadMore() { + CHECK(!paused_); + DCHECK(!handle_watcher_); + + handle_watcher_.reset(new SimpleWatcher( + FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL, task_runner_)); + if (heap_profiler_tag_) + handle_watcher_->set_heap_profiler_tag(heap_profiler_tag_); + MojoResult rv = handle_watcher_->Watch( + message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind(&Connector::OnWatcherHandleReady, base::Unretained(this))); + + if (rv != MOJO_RESULT_OK) { + // If the watch failed because the handle is invalid or its conditions can + // no longer be met, we signal the error asynchronously to avoid reentry. + task_runner_->PostTask( + FROM_HERE, + base::Bind(&Connector::OnWatcherHandleReady, weak_self_, rv)); + } else { + handle_watcher_->ArmOrNotify(); + } + + if (allow_woken_up_by_others_) { + EnsureSyncWatcherExists(); + sync_watcher_->AllowWokenUpBySyncWatchOnSameThread(); + } +} + +bool Connector::ReadSingleMessage(MojoResult* read_result) { + CHECK(!paused_); + + bool receiver_result = false; + + // Detect if |this| was destroyed or the message pipe was closed/transferred + // during message dispatch. + base::WeakPtr weak_self = weak_self_; + + Message message; + const MojoResult rv = ReadMessage(message_pipe_.get(), &message); + *read_result = rv; + + if (rv == MOJO_RESULT_OK) { + base::Optional dispatch_tracker; + if (!is_dispatching_ && nesting_observer_) { + is_dispatching_ = true; + dispatch_tracker.emplace(weak_self); + } + + receiver_result = + incoming_receiver_ && incoming_receiver_->Accept(&message); + + if (!weak_self) + return false; + + if (dispatch_tracker) { + is_dispatching_ = false; + dispatch_tracker.reset(); + } + } else if (rv == MOJO_RESULT_SHOULD_WAIT) { + return true; + } else { + HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false); + return false; + } + + if (enforce_errors_from_incoming_receiver_ && !receiver_result) { + HandleError(true, false); + return false; + } + return true; +} + +void Connector::ReadAllAvailableMessages() { + while (!error_) { + base::WeakPtr weak_self = weak_self_; + MojoResult rv; + + // May delete |this.| + if (!ReadSingleMessage(&rv)) + return; + + if (!weak_self || paused_) + return; + + DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_SHOULD_WAIT); + + if (rv == MOJO_RESULT_SHOULD_WAIT) { + // Attempt to re-arm the Watcher. + MojoResult ready_result; + MojoResult arm_result = handle_watcher_->Arm(&ready_result); + if (arm_result == MOJO_RESULT_OK) + return; + + // The watcher is already ready to notify again. + DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, arm_result); + + if (ready_result == MOJO_RESULT_FAILED_PRECONDITION) { + HandleError(false, false); + return; + } + + // There's more to read now, so we'll just keep looping. + DCHECK_EQ(MOJO_RESULT_OK, ready_result); + } + } +} + +void Connector::CancelWait() { + handle_watcher_.reset(); + sync_watcher_.reset(); +} + +void Connector::HandleError(bool force_pipe_reset, bool force_async_handler) { + if (error_ || !message_pipe_.is_valid()) + return; + + if (paused_) { + // Enforce calling the error handler asynchronously if the user has paused + // receiving messages. We need to wait until the user starts receiving + // messages again. + force_async_handler = true; + } + + if (!force_pipe_reset && force_async_handler) + force_pipe_reset = true; + + if (force_pipe_reset) { + CancelWait(); + internal::MayAutoLock locker(&lock_); + message_pipe_.reset(); + MessagePipe dummy_pipe; + message_pipe_ = std::move(dummy_pipe.handle0); + } else { + CancelWait(); + } + + if (force_async_handler) { + if (!paused_) + WaitToReadMore(); + } else { + error_ = true; + if (!connection_error_handler_.is_null()) + connection_error_handler_.Run(); + } +} + +void Connector::EnsureSyncWatcherExists() { + if (sync_watcher_) + return; + sync_watcher_.reset(new SyncHandleWatcher( + message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind(&Connector::OnSyncHandleWatcherHandleReady, + base::Unretained(this)))); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.cc b/mojo/public/cpp/bindings/lib/control_message_handler.cc new file mode 100644 index 0000000000000000000000000000000000000000..1b7bb78e5fc6173d503fcd37e1b789061d65a3f5 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_handler.cc @@ -0,0 +1,150 @@ +// 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. + +#include "mojo/public/cpp/bindings/lib/control_message_handler.h" + +#include +#include +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" +#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h" + +namespace mojo { +namespace internal { +namespace { + +bool ValidateControlRequestWithResponse(Message* message) { + ValidationContext validation_context(message->payload(), + message->payload_num_bytes(), 0, 0, + message, "ControlRequestValidator"); + if (!ValidateMessageIsRequestExpectingResponse(message, &validation_context)) + return false; + + switch (message->header()->name) { + case interface_control::kRunMessageId: + return ValidateMessagePayload< + interface_control::internal::RunMessageParams_Data>( + message, &validation_context); + } + return false; +} + +bool ValidateControlRequestWithoutResponse(Message* message) { + ValidationContext validation_context(message->payload(), + message->payload_num_bytes(), 0, 0, + message, "ControlRequestValidator"); + if (!ValidateMessageIsRequestWithoutResponse(message, &validation_context)) + return false; + + switch (message->header()->name) { + case interface_control::kRunOrClosePipeMessageId: + return ValidateMessageIsRequestWithoutResponse(message, + &validation_context) && + ValidateMessagePayload< + interface_control::internal::RunOrClosePipeMessageParams_Data>( + message, &validation_context); + } + return false; +} + +} // namespace + +// static +bool ControlMessageHandler::IsControlMessage(const Message* message) { + return message->header()->name == interface_control::kRunMessageId || + message->header()->name == interface_control::kRunOrClosePipeMessageId; +} + +ControlMessageHandler::ControlMessageHandler(uint32_t interface_version) + : interface_version_(interface_version) { +} + +ControlMessageHandler::~ControlMessageHandler() { +} + +bool ControlMessageHandler::Accept(Message* message) { + if (!ValidateControlRequestWithoutResponse(message)) + return false; + + if (message->header()->name == interface_control::kRunOrClosePipeMessageId) + return RunOrClosePipe(message); + + NOTREACHED(); + return false; +} + +bool ControlMessageHandler::AcceptWithResponder( + Message* message, + std::unique_ptr responder) { + if (!ValidateControlRequestWithResponse(message)) + return false; + + if (message->header()->name == interface_control::kRunMessageId) + return Run(message, std::move(responder)); + + NOTREACHED(); + return false; +} + +bool ControlMessageHandler::Run( + Message* message, + std::unique_ptr responder) { + interface_control::internal::RunMessageParams_Data* params = + reinterpret_cast( + message->mutable_payload()); + interface_control::RunMessageParamsPtr params_ptr; + Deserialize(params, ¶ms_ptr, + &context_); + auto& input = *params_ptr->input; + interface_control::RunOutputPtr output = interface_control::RunOutput::New(); + if (input.is_query_version()) { + output->set_query_version_result( + interface_control::QueryVersionResult::New()); + output->get_query_version_result()->version = interface_version_; + } else if (input.is_flush_for_testing()) { + output.reset(); + } else { + output.reset(); + } + + auto response_params_ptr = interface_control::RunResponseMessageParams::New(); + response_params_ptr->output = std::move(output); + size_t size = + PrepareToSerialize( + response_params_ptr, &context_); + MessageBuilder builder(interface_control::kRunMessageId, + Message::kFlagIsResponse, size, 0); + builder.message()->set_request_id(message->request_id()); + + interface_control::internal::RunResponseMessageParams_Data* response_params = + nullptr; + Serialize( + response_params_ptr, builder.buffer(), &response_params, &context_); + ignore_result(responder->Accept(builder.message())); + + return true; +} + +bool ControlMessageHandler::RunOrClosePipe(Message* message) { + interface_control::internal::RunOrClosePipeMessageParams_Data* params = + reinterpret_cast< + interface_control::internal::RunOrClosePipeMessageParams_Data*>( + message->mutable_payload()); + interface_control::RunOrClosePipeMessageParamsPtr params_ptr; + Deserialize( + params, ¶ms_ptr, &context_); + auto& input = *params_ptr->input; + if (input.is_require_version()) + return interface_version_ >= input.get_require_version()->version; + + return false; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.h b/mojo/public/cpp/bindings/lib/control_message_handler.h new file mode 100644 index 0000000000000000000000000000000000000000..5d1f716ea8cf3b9879a5872ce524ff100d9abd13 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_handler.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace internal { + +// Handlers for request messages defined in interface_control_messages.mojom. +class MOJO_CPP_BINDINGS_EXPORT ControlMessageHandler + : NON_EXPORTED_BASE(public MessageReceiverWithResponderStatus) { + public: + static bool IsControlMessage(const Message* message); + + explicit ControlMessageHandler(uint32_t interface_version); + ~ControlMessageHandler() override; + + // Call the following methods only if IsControlMessage() returned true. + bool Accept(Message* message) override; + bool AcceptWithResponder( + Message* message, + std::unique_ptr responder) override; + + private: + bool Run(Message* message, + std::unique_ptr responder); + bool RunOrClosePipe(Message* message); + + uint32_t interface_version_; + SerializationContext context_; + + DISALLOW_COPY_AND_ASSIGN(ControlMessageHandler); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_ diff --git a/mojo/public/cpp/bindings/lib/control_message_proxy.cc b/mojo/public/cpp/bindings/lib/control_message_proxy.cc new file mode 100644 index 0000000000000000000000000000000000000000..d082b49fb31df5f7ce537aab476ffae74d918e52 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_proxy.cc @@ -0,0 +1,188 @@ +// 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. + +#include "mojo/public/cpp/bindings/lib/control_message_proxy.h" + +#include +#include +#include + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h" + +namespace mojo { +namespace internal { + +namespace { + +bool ValidateControlResponse(Message* message) { + ValidationContext validation_context(message->payload(), + message->payload_num_bytes(), 0, 0, + message, "ControlResponseValidator"); + if (!ValidateMessageIsResponse(message, &validation_context)) + return false; + + switch (message->header()->name) { + case interface_control::kRunMessageId: + return ValidateMessagePayload< + interface_control::internal::RunResponseMessageParams_Data>( + message, &validation_context); + } + return false; +} + +using RunCallback = + base::Callback; + +class RunResponseForwardToCallback : public MessageReceiver { + public: + explicit RunResponseForwardToCallback(const RunCallback& callback) + : callback_(callback) {} + bool Accept(Message* message) override; + + private: + RunCallback callback_; + DISALLOW_COPY_AND_ASSIGN(RunResponseForwardToCallback); +}; + +bool RunResponseForwardToCallback::Accept(Message* message) { + if (!ValidateControlResponse(message)) + return false; + + interface_control::internal::RunResponseMessageParams_Data* params = + reinterpret_cast< + interface_control::internal::RunResponseMessageParams_Data*>( + message->mutable_payload()); + interface_control::RunResponseMessageParamsPtr params_ptr; + SerializationContext context; + Deserialize( + params, ¶ms_ptr, &context); + + callback_.Run(std::move(params_ptr)); + return true; +} + +void SendRunMessage(MessageReceiverWithResponder* receiver, + interface_control::RunInputPtr input_ptr, + const RunCallback& callback) { + SerializationContext context; + + auto params_ptr = interface_control::RunMessageParams::New(); + params_ptr->input = std::move(input_ptr); + size_t size = PrepareToSerialize( + params_ptr, &context); + MessageBuilder builder(interface_control::kRunMessageId, + Message::kFlagExpectsResponse, size, 0); + + interface_control::internal::RunMessageParams_Data* params = nullptr; + Serialize( + params_ptr, builder.buffer(), ¶ms, &context); + std::unique_ptr responder = + base::MakeUnique(callback); + ignore_result( + receiver->AcceptWithResponder(builder.message(), std::move(responder))); +} + +Message ConstructRunOrClosePipeMessage( + interface_control::RunOrClosePipeInputPtr input_ptr) { + SerializationContext context; + + auto params_ptr = interface_control::RunOrClosePipeMessageParams::New(); + params_ptr->input = std::move(input_ptr); + + size_t size = PrepareToSerialize< + interface_control::RunOrClosePipeMessageParamsDataView>(params_ptr, + &context); + MessageBuilder builder(interface_control::kRunOrClosePipeMessageId, 0, size, + 0); + + interface_control::internal::RunOrClosePipeMessageParams_Data* params = + nullptr; + Serialize( + params_ptr, builder.buffer(), ¶ms, &context); + return std::move(*builder.message()); +} + +void SendRunOrClosePipeMessage( + MessageReceiverWithResponder* receiver, + interface_control::RunOrClosePipeInputPtr input_ptr) { + Message message(ConstructRunOrClosePipeMessage(std::move(input_ptr))); + + ignore_result(receiver->Accept(&message)); +} + +void RunVersionCallback( + const base::Callback& callback, + interface_control::RunResponseMessageParamsPtr run_response) { + uint32_t version = 0u; + if (run_response->output && run_response->output->is_query_version_result()) + version = run_response->output->get_query_version_result()->version; + callback.Run(version); +} + +void RunClosure(const base::Closure& callback, + interface_control::RunResponseMessageParamsPtr run_response) { + callback.Run(); +} + +} // namespace + +ControlMessageProxy::ControlMessageProxy(MessageReceiverWithResponder* receiver) + : receiver_(receiver) { +} + +ControlMessageProxy::~ControlMessageProxy() = default; + +void ControlMessageProxy::QueryVersion( + const base::Callback& callback) { + auto input_ptr = interface_control::RunInput::New(); + input_ptr->set_query_version(interface_control::QueryVersion::New()); + SendRunMessage(receiver_, std::move(input_ptr), + base::Bind(&RunVersionCallback, callback)); +} + +void ControlMessageProxy::RequireVersion(uint32_t version) { + auto require_version = interface_control::RequireVersion::New(); + require_version->version = version; + auto input_ptr = interface_control::RunOrClosePipeInput::New(); + input_ptr->set_require_version(std::move(require_version)); + SendRunOrClosePipeMessage(receiver_, std::move(input_ptr)); +} + +void ControlMessageProxy::FlushForTesting() { + if (encountered_error_) + return; + + auto input_ptr = interface_control::RunInput::New(); + input_ptr->set_flush_for_testing(interface_control::FlushForTesting::New()); + base::RunLoop run_loop; + run_loop_quit_closure_ = run_loop.QuitClosure(); + SendRunMessage( + receiver_, std::move(input_ptr), + base::Bind(&RunClosure, + base::Bind(&ControlMessageProxy::RunFlushForTestingClosure, + base::Unretained(this)))); + run_loop.Run(); +} + +void ControlMessageProxy::RunFlushForTestingClosure() { + DCHECK(!run_loop_quit_closure_.is_null()); + base::ResetAndReturn(&run_loop_quit_closure_).Run(); +} + +void ControlMessageProxy::OnConnectionError() { + encountered_error_ = true; + if (!run_loop_quit_closure_.is_null()) + RunFlushForTestingClosure(); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/control_message_proxy.h b/mojo/public/cpp/bindings/lib/control_message_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..2f9314ebf0023edf3646ce62e77e969dc0a72489 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_proxy.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_ + +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" + +namespace mojo { + +class MessageReceiverWithResponder; + +namespace internal { + +// Proxy for request messages defined in interface_control_messages.mojom. +class MOJO_CPP_BINDINGS_EXPORT ControlMessageProxy { + public: + // Doesn't take ownership of |receiver|. It must outlive this object. + explicit ControlMessageProxy(MessageReceiverWithResponder* receiver); + ~ControlMessageProxy(); + + void QueryVersion(const base::Callback& callback); + void RequireVersion(uint32_t version); + + void FlushForTesting(); + void OnConnectionError(); + + private: + void RunFlushForTestingClosure(); + + // Not owned. + MessageReceiverWithResponder* receiver_; + bool encountered_error_ = false; + + base::Closure run_loop_quit_closure_; + + DISALLOW_COPY_AND_ASSIGN(ControlMessageProxy); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_ diff --git a/mojo/public/cpp/bindings/lib/equals_traits.h b/mojo/public/cpp/bindings/lib/equals_traits.h new file mode 100644 index 0000000000000000000000000000000000000000..53c7dce6931d2b81fd3f9d3ec14c7b2ca9f86adc --- /dev/null +++ b/mojo/public/cpp/bindings/lib/equals_traits.h @@ -0,0 +1,94 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_ + +#include +#include +#include + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { +namespace internal { + +template +struct HasEqualsMethod { + template + static char Test(decltype(&U::Equals)); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template ::value> +struct EqualsTraits; + +template +bool Equals(const T& a, const T& b); + +template +struct EqualsTraits { + static bool Equals(const T& a, const T& b) { return a.Equals(b); } +}; + +template +struct EqualsTraits { + static bool Equals(const T& a, const T& b) { return a == b; } +}; + +template +struct EqualsTraits, false> { + static bool Equals(const base::Optional& a, const base::Optional& b) { + if (!a && !b) + return true; + if (!a || !b) + return false; + + return internal::Equals(*a, *b); + } +}; + +template +struct EqualsTraits, false> { + static bool Equals(const std::vector& a, const std::vector& b) { + if (a.size() != b.size()) + return false; + for (size_t i = 0; i < a.size(); ++i) { + if (!internal::Equals(a[i], b[i])) + return false; + } + return true; + } +}; + +template +struct EqualsTraits, false> { + static bool Equals(const std::unordered_map& a, + const std::unordered_map& b) { + if (a.size() != b.size()) + return false; + for (const auto& element : a) { + auto iter = b.find(element.first); + if (iter == b.end() || !internal::Equals(element.second, iter->second)) + return false; + } + return true; + } +}; + +template +bool Equals(const T& a, const T& b) { + return EqualsTraits::Equals(a, b); +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/lib/filter_chain.cc b/mojo/public/cpp/bindings/lib/filter_chain.cc new file mode 100644 index 0000000000000000000000000000000000000000..5d919fe17255978b556a7ff3cdc43ada4e9e6fb4 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/filter_chain.cc @@ -0,0 +1,47 @@ +// Copyright 2014 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. + +#include "mojo/public/cpp/bindings/filter_chain.h" + +#include + +#include "base/logging.h" + +namespace mojo { + +FilterChain::FilterChain(MessageReceiver* sink) : sink_(sink) { +} + +FilterChain::FilterChain(FilterChain&& other) : sink_(other.sink_) { + other.sink_ = nullptr; + filters_.swap(other.filters_); +} + +FilterChain& FilterChain::operator=(FilterChain&& other) { + std::swap(sink_, other.sink_); + filters_.swap(other.filters_); + return *this; +} + +FilterChain::~FilterChain() { +} + +void FilterChain::SetSink(MessageReceiver* sink) { + DCHECK(!sink_); + sink_ = sink; +} + +bool FilterChain::Accept(Message* message) { + DCHECK(sink_); + for (auto& filter : filters_) + if (!filter->Accept(message)) + return false; + return sink_->Accept(message); +} + +void FilterChain::Append(std::unique_ptr filter) { + filters_.emplace_back(std::move(filter)); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/fixed_buffer.cc b/mojo/public/cpp/bindings/lib/fixed_buffer.cc new file mode 100644 index 0000000000000000000000000000000000000000..725a193cd740940948c6d7939cdf10d22cfb6034 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/fixed_buffer.cc @@ -0,0 +1,30 @@ +// Copyright 2014 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. + +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" + +#include + +namespace mojo { +namespace internal { + +FixedBufferForTesting::FixedBufferForTesting(size_t size) { + size = internal::Align(size); + // Use calloc here to ensure all message memory is zero'd out. + void* ptr = calloc(size, 1); + Initialize(ptr, size); +} + +FixedBufferForTesting::~FixedBufferForTesting() { + free(data()); +} + +void* FixedBufferForTesting::Leak() { + void* ptr = data(); + Initialize(nullptr, 0); + return ptr; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/fixed_buffer.h b/mojo/public/cpp/bindings/lib/fixed_buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..070b0c8cef482ff3476d8a3fa6a8c4c510571374 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/fixed_buffer.h @@ -0,0 +1,39 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" + +namespace mojo { +namespace internal { + +// FixedBufferForTesting owns its buffer. The Leak method may be used to steal +// the underlying memory. +class MOJO_CPP_BINDINGS_EXPORT FixedBufferForTesting + : NON_EXPORTED_BASE(public Buffer) { + public: + explicit FixedBufferForTesting(size_t size); + ~FixedBufferForTesting(); + + // Returns the internal memory owned by the Buffer to the caller. The Buffer + // relinquishes its pointer, effectively resetting the state of the Buffer + // and leaving the caller responsible for freeing the returned memory address + // when no longer needed. + void* Leak(); + + private: + DISALLOW_COPY_AND_ASSIGN(FixedBufferForTesting); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ diff --git a/mojo/public/cpp/bindings/lib/handle_interface_serialization.h b/mojo/public/cpp/bindings/lib/handle_interface_serialization.h new file mode 100644 index 0000000000000000000000000000000000000000..14ed21f0ac822fa6af8aad2dcf8bb01843e8cdfe --- /dev/null +++ b/mojo/public/cpp/bindings/lib/handle_interface_serialization.h @@ -0,0 +1,181 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_INTERFACE_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_INTERFACE_SERIALIZATION_H_ + +#include + +#include "mojo/public/cpp/bindings/associated_group_controller.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/interface_data_view.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { +namespace internal { + +template +struct Serializer, + AssociatedInterfacePtrInfo> { + static_assert(std::is_base_of::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const AssociatedInterfacePtrInfo& input, + SerializationContext* context) { + if (input.handle().is_valid()) + context->associated_endpoint_count++; + return 0; + } + + static void Serialize(AssociatedInterfacePtrInfo& input, + AssociatedInterface_Data* output, + SerializationContext* context) { + DCHECK(!input.handle().is_valid() || input.handle().pending_association()); + if (input.handle().is_valid()) { + // Set to the index of the element pushed to the back of the vector. + output->handle.value = + static_cast(context->associated_endpoint_handles.size()); + context->associated_endpoint_handles.push_back(input.PassHandle()); + } else { + output->handle.value = kEncodedInvalidHandleValue; + } + output->version = input.version(); + } + + static bool Deserialize(AssociatedInterface_Data* input, + AssociatedInterfacePtrInfo* output, + SerializationContext* context) { + if (input->handle.is_valid()) { + DCHECK_LT(input->handle.value, + context->associated_endpoint_handles.size()); + output->set_handle( + std::move(context->associated_endpoint_handles[input->handle.value])); + } else { + output->set_handle(ScopedInterfaceEndpointHandle()); + } + output->set_version(input->version); + return true; + } +}; + +template +struct Serializer, + AssociatedInterfaceRequest> { + static_assert(std::is_base_of::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const AssociatedInterfaceRequest& input, + SerializationContext* context) { + if (input.handle().is_valid()) + context->associated_endpoint_count++; + return 0; + } + + static void Serialize(AssociatedInterfaceRequest& input, + AssociatedEndpointHandle_Data* output, + SerializationContext* context) { + DCHECK(!input.handle().is_valid() || input.handle().pending_association()); + if (input.handle().is_valid()) { + // Set to the index of the element pushed to the back of the vector. + output->value = + static_cast(context->associated_endpoint_handles.size()); + context->associated_endpoint_handles.push_back(input.PassHandle()); + } else { + output->value = kEncodedInvalidHandleValue; + } + } + + static bool Deserialize(AssociatedEndpointHandle_Data* input, + AssociatedInterfaceRequest* output, + SerializationContext* context) { + if (input->is_valid()) { + DCHECK_LT(input->value, context->associated_endpoint_handles.size()); + output->Bind( + std::move(context->associated_endpoint_handles[input->value])); + } else { + output->Bind(ScopedInterfaceEndpointHandle()); + } + return true; + } +}; + +template +struct Serializer, InterfacePtr> { + static_assert(std::is_base_of::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const InterfacePtr& input, + SerializationContext* context) { + return 0; + } + + static void Serialize(InterfacePtr& input, + Interface_Data* output, + SerializationContext* context) { + InterfacePtrInfo info = input.PassInterface(); + output->handle = context->handles.AddHandle(info.PassHandle().release()); + output->version = info.version(); + } + + static bool Deserialize(Interface_Data* input, + InterfacePtr* output, + SerializationContext* context) { + output->Bind(InterfacePtrInfo( + context->handles.TakeHandleAs(input->handle), + input->version)); + return true; + } +}; + +template +struct Serializer, InterfaceRequest> { + static_assert(std::is_base_of::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const InterfaceRequest& input, + SerializationContext* context) { + return 0; + } + + static void Serialize(InterfaceRequest& input, + Handle_Data* output, + SerializationContext* context) { + *output = context->handles.AddHandle(input.PassMessagePipe().release()); + } + + static bool Deserialize(Handle_Data* input, + InterfaceRequest* output, + SerializationContext* context) { + output->Bind(context->handles.TakeHandleAs(*input)); + return true; + } +}; + +template +struct Serializer, ScopedHandleBase> { + static size_t PrepareToSerialize(const ScopedHandleBase& input, + SerializationContext* context) { + return 0; + } + + static void Serialize(ScopedHandleBase& input, + Handle_Data* output, + SerializationContext* context) { + *output = context->handles.AddHandle(input.release()); + } + + static bool Deserialize(Handle_Data* input, + ScopedHandleBase* output, + SerializationContext* context) { + *output = context->handles.TakeHandleAs(*input); + return true; + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_INTERFACE_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/hash_util.h b/mojo/public/cpp/bindings/lib/hash_util.h new file mode 100644 index 0000000000000000000000000000000000000000..93280d69daf2f4842e74302f0cc7af7480530c2c --- /dev/null +++ b/mojo/public/cpp/bindings/lib/hash_util.h @@ -0,0 +1,84 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_HASH_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_HASH_UTIL_H_ + +#include +#include +#include +#include + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { +namespace internal { + +template +size_t HashCombine(size_t seed, const T& value) { + // Based on proposal in: + // http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf + return seed ^ (std::hash()(value) + (seed << 6) + (seed >> 2)); +} + +template +struct HasHashMethod { + template + static char Test(decltype(&U::Hash)); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template ::value> +struct HashTraits; + +template +size_t Hash(size_t seed, const T& value); + +template +struct HashTraits { + static size_t Hash(size_t seed, const T& value) { return value.Hash(seed); } +}; + +template +struct HashTraits { + static size_t Hash(size_t seed, const T& value) { + return HashCombine(seed, value); + } +}; + +template +struct HashTraits, false> { + static size_t Hash(size_t seed, const std::vector& value) { + for (const auto& element : value) { + seed = HashCombine(seed, element); + } + return seed; + } +}; + +template +struct HashTraits>, false> { + static size_t Hash(size_t seed, const base::Optional>& value) { + if (!value) + return HashCombine(seed, 0); + + return Hash(seed, *value); + } +}; + +template +size_t Hash(size_t seed, const T& value) { + return HashTraits::Hash(seed, value); +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_HASH_UTIL_H_ diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc new file mode 100644 index 0000000000000000000000000000000000000000..4682e72fadc43f1b905f00037d19a6124a55dfdc --- /dev/null +++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc @@ -0,0 +1,412 @@ +// 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. + +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" + +#include + +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "mojo/public/cpp/bindings/associated_group.h" +#include "mojo/public/cpp/bindings/associated_group_controller.h" +#include "mojo/public/cpp/bindings/interface_endpoint_controller.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" +#include "mojo/public/cpp/bindings/sync_call_restrictions.h" + +namespace mojo { + +// ---------------------------------------------------------------------------- + +namespace { + +void DCheckIfInvalid(const base::WeakPtr& client, + const std::string& message) { + bool is_valid = client && !client->encountered_error(); + DCHECK(!is_valid) << message; +} + +// When receiving an incoming message which expects a repsonse, +// InterfaceEndpointClient creates a ResponderThunk object and passes it to the +// incoming message receiver. When the receiver finishes processing the message, +// it can provide a response using this object. +class ResponderThunk : public MessageReceiverWithStatus { + public: + explicit ResponderThunk( + const base::WeakPtr& endpoint_client, + scoped_refptr runner) + : endpoint_client_(endpoint_client), + accept_was_invoked_(false), + task_runner_(std::move(runner)) {} + ~ResponderThunk() override { + if (!accept_was_invoked_) { + // The Service handled a message that was expecting a response + // but did not send a response. + // We raise an error to signal the calling application that an error + // condition occurred. Without this the calling application would have no + // way of knowing it should stop waiting for a response. + if (task_runner_->RunsTasksOnCurrentThread()) { + // Please note that even if this code is run from a different task + // runner on the same thread as |task_runner_|, it is okay to directly + // call InterfaceEndpointClient::RaiseError(), because it will raise + // error from the correct task runner asynchronously. + if (endpoint_client_) { + endpoint_client_->RaiseError(); + } + } else { + task_runner_->PostTask( + FROM_HERE, + base::Bind(&InterfaceEndpointClient::RaiseError, endpoint_client_)); + } + } + } + + // MessageReceiver implementation: + bool Accept(Message* message) override { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + accept_was_invoked_ = true; + DCHECK(message->has_flag(Message::kFlagIsResponse)); + + bool result = false; + + if (endpoint_client_) + result = endpoint_client_->Accept(message); + + return result; + } + + // MessageReceiverWithStatus implementation: + bool IsValid() override { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + return endpoint_client_ && !endpoint_client_->encountered_error(); + } + + void DCheckInvalid(const std::string& message) override { + if (task_runner_->RunsTasksOnCurrentThread()) { + DCheckIfInvalid(endpoint_client_, message); + } else { + task_runner_->PostTask( + FROM_HERE, base::Bind(&DCheckIfInvalid, endpoint_client_, message)); + } + } + + private: + base::WeakPtr endpoint_client_; + bool accept_was_invoked_; + scoped_refptr task_runner_; + + DISALLOW_COPY_AND_ASSIGN(ResponderThunk); +}; + +} // namespace + +// ---------------------------------------------------------------------------- + +InterfaceEndpointClient::SyncResponseInfo::SyncResponseInfo( + bool* in_response_received) + : response_received(in_response_received) {} + +InterfaceEndpointClient::SyncResponseInfo::~SyncResponseInfo() {} + +// ---------------------------------------------------------------------------- + +InterfaceEndpointClient::HandleIncomingMessageThunk::HandleIncomingMessageThunk( + InterfaceEndpointClient* owner) + : owner_(owner) {} + +InterfaceEndpointClient::HandleIncomingMessageThunk:: + ~HandleIncomingMessageThunk() {} + +bool InterfaceEndpointClient::HandleIncomingMessageThunk::Accept( + Message* message) { + return owner_->HandleValidatedMessage(message); +} + +// ---------------------------------------------------------------------------- + +InterfaceEndpointClient::InterfaceEndpointClient( + ScopedInterfaceEndpointHandle handle, + MessageReceiverWithResponderStatus* receiver, + std::unique_ptr payload_validator, + bool expect_sync_requests, + scoped_refptr runner, + uint32_t interface_version) + : expect_sync_requests_(expect_sync_requests), + handle_(std::move(handle)), + incoming_receiver_(receiver), + thunk_(this), + filters_(&thunk_), + task_runner_(std::move(runner)), + control_message_proxy_(this), + control_message_handler_(interface_version), + weak_ptr_factory_(this) { + DCHECK(handle_.is_valid()); + + // TODO(yzshen): the way to use validator (or message filter in general) + // directly is a little awkward. + if (payload_validator) + filters_.Append(std::move(payload_validator)); + + if (handle_.pending_association()) { + handle_.SetAssociationEventHandler(base::Bind( + &InterfaceEndpointClient::OnAssociationEvent, base::Unretained(this))); + } else { + InitControllerIfNecessary(); + } +} + +InterfaceEndpointClient::~InterfaceEndpointClient() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (controller_) + handle_.group_controller()->DetachEndpointClient(handle_); +} + +AssociatedGroup* InterfaceEndpointClient::associated_group() { + if (!associated_group_) + associated_group_ = base::MakeUnique(handle_); + return associated_group_.get(); +} + +ScopedInterfaceEndpointHandle InterfaceEndpointClient::PassHandle() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!has_pending_responders()); + + if (!handle_.is_valid()) + return ScopedInterfaceEndpointHandle(); + + handle_.SetAssociationEventHandler( + ScopedInterfaceEndpointHandle::AssociationEventCallback()); + + if (controller_) { + controller_ = nullptr; + handle_.group_controller()->DetachEndpointClient(handle_); + } + + return std::move(handle_); +} + +void InterfaceEndpointClient::AddFilter( + std::unique_ptr filter) { + filters_.Append(std::move(filter)); +} + +void InterfaceEndpointClient::RaiseError() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!handle_.pending_association()) + handle_.group_controller()->RaiseError(); +} + +void InterfaceEndpointClient::CloseWithReason(uint32_t custom_reason, + const std::string& description) { + DCHECK(thread_checker_.CalledOnValidThread()); + + auto handle = PassHandle(); + handle.ResetWithReason(custom_reason, description); +} + +bool InterfaceEndpointClient::Accept(Message* message) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!message->has_flag(Message::kFlagExpectsResponse)); + DCHECK(!handle_.pending_association()); + + // This has to been done even if connection error has occurred. For example, + // the message contains a pending associated request. The user may try to use + // the corresponding associated interface pointer after sending this message. + // That associated interface pointer has to join an associated group in order + // to work properly. + if (!message->associated_endpoint_handles()->empty()) + message->SerializeAssociatedEndpointHandles(handle_.group_controller()); + + if (encountered_error_) + return false; + + InitControllerIfNecessary(); + + return controller_->SendMessage(message); +} + +bool InterfaceEndpointClient::AcceptWithResponder( + Message* message, + std::unique_ptr responder) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(message->has_flag(Message::kFlagExpectsResponse)); + DCHECK(!handle_.pending_association()); + + // Please see comments in Accept(). + if (!message->associated_endpoint_handles()->empty()) + message->SerializeAssociatedEndpointHandles(handle_.group_controller()); + + if (encountered_error_) + return false; + + InitControllerIfNecessary(); + + // Reserve 0 in case we want it to convey special meaning in the future. + uint64_t request_id = next_request_id_++; + if (request_id == 0) + request_id = next_request_id_++; + + message->set_request_id(request_id); + + bool is_sync = message->has_flag(Message::kFlagIsSync); + if (!controller_->SendMessage(message)) + return false; + + if (!is_sync) { + async_responders_[request_id] = std::move(responder); + return true; + } + + SyncCallRestrictions::AssertSyncCallAllowed(); + + bool response_received = false; + sync_responses_.insert(std::make_pair( + request_id, base::MakeUnique(&response_received))); + + base::WeakPtr weak_self = + weak_ptr_factory_.GetWeakPtr(); + controller_->SyncWatch(&response_received); + // Make sure that this instance hasn't been destroyed. + if (weak_self) { + DCHECK(base::ContainsKey(sync_responses_, request_id)); + auto iter = sync_responses_.find(request_id); + DCHECK_EQ(&response_received, iter->second->response_received); + if (response_received) + ignore_result(responder->Accept(&iter->second->response)); + sync_responses_.erase(iter); + } + + return true; +} + +bool InterfaceEndpointClient::HandleIncomingMessage(Message* message) { + DCHECK(thread_checker_.CalledOnValidThread()); + return filters_.Accept(message); +} + +void InterfaceEndpointClient::NotifyError( + const base::Optional& reason) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (encountered_error_) + return; + encountered_error_ = true; + + // Response callbacks may hold on to resource, and there's no need to keep + // them alive any longer. Note that it's allowed that a pending response + // callback may own this endpoint, so we simply move the responders onto the + // stack here and let them be destroyed when the stack unwinds. + AsyncResponderMap responders = std::move(async_responders_); + + control_message_proxy_.OnConnectionError(); + + if (!error_handler_.is_null()) { + base::Closure error_handler = std::move(error_handler_); + error_handler.Run(); + } else if (!error_with_reason_handler_.is_null()) { + ConnectionErrorWithReasonCallback error_with_reason_handler = + std::move(error_with_reason_handler_); + if (reason) { + error_with_reason_handler.Run(reason->custom_reason, reason->description); + } else { + error_with_reason_handler.Run(0, std::string()); + } + } +} + +void InterfaceEndpointClient::QueryVersion( + const base::Callback& callback) { + control_message_proxy_.QueryVersion(callback); +} + +void InterfaceEndpointClient::RequireVersion(uint32_t version) { + control_message_proxy_.RequireVersion(version); +} + +void InterfaceEndpointClient::FlushForTesting() { + control_message_proxy_.FlushForTesting(); +} + +void InterfaceEndpointClient::InitControllerIfNecessary() { + if (controller_ || handle_.pending_association()) + return; + + controller_ = handle_.group_controller()->AttachEndpointClient(handle_, this, + task_runner_); + if (expect_sync_requests_) + controller_->AllowWokenUpBySyncWatchOnSameThread(); +} + +void InterfaceEndpointClient::OnAssociationEvent( + ScopedInterfaceEndpointHandle::AssociationEvent event) { + if (event == ScopedInterfaceEndpointHandle::ASSOCIATED) { + InitControllerIfNecessary(); + } else if (event == + ScopedInterfaceEndpointHandle::PEER_CLOSED_BEFORE_ASSOCIATION) { + task_runner_->PostTask(FROM_HERE, + base::Bind(&InterfaceEndpointClient::NotifyError, + weak_ptr_factory_.GetWeakPtr(), + handle_.disconnect_reason())); + } +} + +bool InterfaceEndpointClient::HandleValidatedMessage(Message* message) { + DCHECK_EQ(handle_.id(), message->interface_id()); + + if (encountered_error_) { + // This message is received after error has been encountered. For associated + // interfaces, this means the remote side sends a + // PeerAssociatedEndpointClosed event but continues to send more messages + // for the same interface. Close the pipe because this shouldn't happen. + DVLOG(1) << "A message is received for an interface after it has been " + << "disconnected. Closing the pipe."; + return false; + } + + if (message->has_flag(Message::kFlagExpectsResponse)) { + std::unique_ptr responder = + base::MakeUnique(weak_ptr_factory_.GetWeakPtr(), + task_runner_); + if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) { + return control_message_handler_.AcceptWithResponder(message, + std::move(responder)); + } else { + return incoming_receiver_->AcceptWithResponder(message, + std::move(responder)); + } + } else if (message->has_flag(Message::kFlagIsResponse)) { + uint64_t request_id = message->request_id(); + + if (message->has_flag(Message::kFlagIsSync)) { + auto it = sync_responses_.find(request_id); + if (it == sync_responses_.end()) + return false; + it->second->response = std::move(*message); + *it->second->response_received = true; + return true; + } + + auto it = async_responders_.find(request_id); + if (it == async_responders_.end()) + return false; + std::unique_ptr responder = std::move(it->second); + async_responders_.erase(it); + return responder->Accept(message); + } else { + if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) + return control_message_handler_.Accept(message); + + return incoming_receiver_->Accept(message); + } +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.h b/mojo/public/cpp/bindings/lib/interface_ptr_state.h new file mode 100644 index 0000000000000000000000000000000000000000..fa54979795567b7ef48f78a8e43387a362334563 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.h @@ -0,0 +1,226 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_STATE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_STATE_H_ + +#include + +#include // For |std::swap()|. +#include +#include +#include + +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "mojo/public/cpp/bindings/associated_group.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/filter_chain.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" +#include "mojo/public/cpp/bindings/message_header_validator.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { +namespace internal { + +template +class InterfacePtrState { + public: + InterfacePtrState() : version_(0u) {} + + ~InterfacePtrState() { + endpoint_client_.reset(); + proxy_.reset(); + if (router_) + router_->CloseMessagePipe(); + } + + Interface* instance() { + ConfigureProxyIfNecessary(); + + // This will be null if the object is not bound. + return proxy_.get(); + } + + uint32_t version() const { return version_; } + + void QueryVersion(const base::Callback& callback) { + ConfigureProxyIfNecessary(); + + // It is safe to capture |this| because the callback won't be run after this + // object goes away. + endpoint_client_->QueryVersion(base::Bind( + &InterfacePtrState::OnQueryVersion, base::Unretained(this), callback)); + } + + void RequireVersion(uint32_t version) { + ConfigureProxyIfNecessary(); + + if (version <= version_) + return; + + version_ = version; + endpoint_client_->RequireVersion(version); + } + + void FlushForTesting() { + ConfigureProxyIfNecessary(); + endpoint_client_->FlushForTesting(); + } + + void CloseWithReason(uint32_t custom_reason, const std::string& description) { + ConfigureProxyIfNecessary(); + endpoint_client_->CloseWithReason(custom_reason, description); + } + + void Swap(InterfacePtrState* other) { + using std::swap; + swap(other->router_, router_); + swap(other->endpoint_client_, endpoint_client_); + swap(other->proxy_, proxy_); + handle_.swap(other->handle_); + runner_.swap(other->runner_); + swap(other->version_, version_); + } + + void Bind(InterfacePtrInfo info, + scoped_refptr runner) { + DCHECK(!router_); + DCHECK(!endpoint_client_); + DCHECK(!proxy_); + DCHECK(!handle_.is_valid()); + DCHECK_EQ(0u, version_); + DCHECK(info.is_valid()); + + handle_ = info.PassHandle(); + version_ = info.version(); + runner_ = std::move(runner); + } + + bool HasAssociatedInterfaces() const { + return router_ ? router_->HasAssociatedEndpoints() : false; + } + + // After this method is called, the object is in an invalid state and + // shouldn't be reused. + InterfacePtrInfo PassInterface() { + endpoint_client_.reset(); + proxy_.reset(); + return InterfacePtrInfo( + router_ ? router_->PassMessagePipe() : std::move(handle_), version_); + } + + bool is_bound() const { return handle_.is_valid() || endpoint_client_; } + + bool encountered_error() const { + return endpoint_client_ ? endpoint_client_->encountered_error() : false; + } + + void set_connection_error_handler(const base::Closure& error_handler) { + ConfigureProxyIfNecessary(); + + DCHECK(endpoint_client_); + endpoint_client_->set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + ConfigureProxyIfNecessary(); + + DCHECK(endpoint_client_); + endpoint_client_->set_connection_error_with_reason_handler(error_handler); + } + + // Returns true if bound and awaiting a response to a message. + bool has_pending_callbacks() const { + return endpoint_client_ && endpoint_client_->has_pending_responders(); + } + + AssociatedGroup* associated_group() { + ConfigureProxyIfNecessary(); + return endpoint_client_->associated_group(); + } + + void EnableTestingMode() { + ConfigureProxyIfNecessary(); + router_->EnableTestingMode(); + } + + void ForwardMessage(Message message) { + ConfigureProxyIfNecessary(); + endpoint_client_->Accept(&message); + } + + void ForwardMessageWithResponder(Message message, + std::unique_ptr responder) { + ConfigureProxyIfNecessary(); + endpoint_client_->AcceptWithResponder(&message, std::move(responder)); + } + + private: + using Proxy = typename Interface::Proxy_; + + void ConfigureProxyIfNecessary() { + // The proxy has been configured. + if (proxy_) { + DCHECK(router_); + DCHECK(endpoint_client_); + return; + } + // The object hasn't been bound. + if (!handle_.is_valid()) + return; + + MultiplexRouter::Config config = + Interface::PassesAssociatedKinds_ + ? MultiplexRouter::MULTI_INTERFACE + : (Interface::HasSyncMethods_ + ? MultiplexRouter::SINGLE_INTERFACE_WITH_SYNC_METHODS + : MultiplexRouter::SINGLE_INTERFACE); + router_ = new MultiplexRouter(std::move(handle_), config, true, runner_); + router_->SetMasterInterfaceName(Interface::Name_); + endpoint_client_.reset(new InterfaceEndpointClient( + router_->CreateLocalEndpointHandle(kMasterInterfaceId), nullptr, + base::WrapUnique(new typename Interface::ResponseValidator_()), false, + std::move(runner_), + // The version is only queried from the client so the value passed here + // will not be used. + 0u)); + proxy_.reset(new Proxy(endpoint_client_.get())); + } + + void OnQueryVersion(const base::Callback& callback, + uint32_t version) { + version_ = version; + callback.Run(version); + } + + scoped_refptr router_; + + std::unique_ptr endpoint_client_; + std::unique_ptr proxy_; + + // |router_| (as well as other members above) is not initialized until + // read/write with the message pipe handle is needed. |handle_| is valid + // between the Bind() call and the initialization of |router_|. + ScopedMessagePipeHandle handle_; + scoped_refptr runner_; + + uint32_t version_; + + DISALLOW_COPY_AND_ASSIGN(InterfacePtrState); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_STATE_H_ diff --git a/mojo/public/cpp/bindings/lib/map_data_internal.h b/mojo/public/cpp/bindings/lib/map_data_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..f8e3d2918fc0b1c2a8fec30a4d95e2313391e6f1 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/map_data_internal.h @@ -0,0 +1,85 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_ + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" + +namespace mojo { +namespace internal { + +// Map serializes into a struct which has two arrays as struct fields, the keys +// and the values. +template +class Map_Data { + public: + static Map_Data* New(Buffer* buf) { + return new (buf->Allocate(sizeof(Map_Data))) Map_Data(); + } + + // |validate_params| must have non-null |key_validate_params| and + // |element_validate_params| members. + static bool Validate(const void* data, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + if (!data) + return true; + + if (!ValidateStructHeaderAndClaimMemory(data, validation_context)) + return false; + + const Map_Data* object = static_cast(data); + if (object->header_.num_bytes != sizeof(Map_Data) || + object->header_.version != 0) { + ReportValidationError(validation_context, + VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + + if (!ValidatePointerNonNullable( + object->keys, "null key array in map struct", validation_context) || + !ValidateContainer(object->keys, validation_context, + validate_params->key_validate_params)) { + return false; + } + + if (!ValidatePointerNonNullable(object->values, + "null value array in map struct", + validation_context) || + !ValidateContainer(object->values, validation_context, + validate_params->element_validate_params)) { + return false; + } + + if (object->keys.Get()->size() != object->values.Get()->size()) { + ReportValidationError(validation_context, + VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP); + return false; + } + + return true; + } + + StructHeader header_; + + Pointer> keys; + Pointer> values; + + private: + Map_Data() { + header_.num_bytes = sizeof(*this); + header_.version = 0; + } + ~Map_Data() = delete; +}; +static_assert(sizeof(Map_Data) == 24, "Bad sizeof(Map_Data)"); + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/map_serialization.h b/mojo/public/cpp/bindings/lib/map_serialization.h new file mode 100644 index 0000000000000000000000000000000000000000..718a76307db88c6050b3b766b001081e9d05cd39 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/map_serialization.h @@ -0,0 +1,182 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_ + +#include +#include + +#include "mojo/public/cpp/bindings/array_data_view.h" +#include "mojo/public/cpp/bindings/lib/array_serialization.h" +#include "mojo/public/cpp/bindings/lib/map_data_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/bindings/map_data_view.h" + +namespace mojo { +namespace internal { + +template +class MapReaderBase { + public: + using UserType = typename std::remove_const::type; + using Traits = MapTraits; + using MaybeConstIterator = + decltype(Traits::GetBegin(std::declval())); + + explicit MapReaderBase(MaybeConstUserType& input) + : input_(input), iter_(Traits::GetBegin(input_)) {} + ~MapReaderBase() {} + + size_t GetSize() const { return Traits::GetSize(input_); } + + // Return null because key or value elements are not stored continuously in + // memory. + void* GetDataIfExists() { return nullptr; } + + protected: + MaybeConstUserType& input_; + MaybeConstIterator iter_; +}; + +// Used as the UserTypeReader template parameter of ArraySerializer. +template +class MapKeyReader : public MapReaderBase { + public: + using Base = MapReaderBase; + using Traits = typename Base::Traits; + using MaybeConstIterator = typename Base::MaybeConstIterator; + + explicit MapKeyReader(MaybeConstUserType& input) : Base(input) {} + ~MapKeyReader() {} + + using GetNextResult = + decltype(Traits::GetKey(std::declval())); + GetNextResult GetNext() { + GetNextResult key = Traits::GetKey(this->iter_); + Traits::AdvanceIterator(this->iter_); + return key; + } +}; + +// Used as the UserTypeReader template parameter of ArraySerializer. +template +class MapValueReader : public MapReaderBase { + public: + using Base = MapReaderBase; + using Traits = typename Base::Traits; + using MaybeConstIterator = typename Base::MaybeConstIterator; + + explicit MapValueReader(MaybeConstUserType& input) : Base(input) {} + ~MapValueReader() {} + + using GetNextResult = + decltype(Traits::GetValue(std::declval())); + GetNextResult GetNext() { + GetNextResult value = Traits::GetValue(this->iter_); + Traits::AdvanceIterator(this->iter_); + return value; + } +}; + +template +struct Serializer, MaybeConstUserType> { + using UserType = typename std::remove_const::type; + using Traits = MapTraits; + using UserKey = typename Traits::Key; + using UserValue = typename Traits::Value; + using Data = typename MojomTypeTraits>::Data; + using KeyArraySerializer = ArraySerializer, + std::vector, + MapKeyReader>; + using ValueArraySerializer = + ArraySerializer, + std::vector, + MapValueReader>; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + SerializationContext* context) { + if (CallIsNullIfExists(input)) + return 0; + + size_t struct_overhead = sizeof(Data); + MapKeyReader key_reader(input); + size_t keys_size = + KeyArraySerializer::GetSerializedSize(&key_reader, context); + MapValueReader value_reader(input); + size_t values_size = + ValueArraySerializer::GetSerializedSize(&value_reader, context); + + return struct_overhead + keys_size + values_size; + } + + static void Serialize(MaybeConstUserType& input, + Buffer* buf, + Data** output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + DCHECK(validate_params->key_validate_params); + DCHECK(validate_params->element_validate_params); + if (CallIsNullIfExists(input)) { + *output = nullptr; + return; + } + + auto result = Data::New(buf); + if (result) { + auto keys_ptr = MojomTypeTraits>::Data::New( + Traits::GetSize(input), buf); + if (keys_ptr) { + MapKeyReader key_reader(input); + KeyArraySerializer::SerializeElements( + &key_reader, buf, keys_ptr, validate_params->key_validate_params, + context); + result->keys.Set(keys_ptr); + } + + auto values_ptr = MojomTypeTraits>::Data::New( + Traits::GetSize(input), buf); + if (values_ptr) { + MapValueReader value_reader(input); + ValueArraySerializer::SerializeElements( + &value_reader, buf, values_ptr, + validate_params->element_validate_params, context); + result->values.Set(values_ptr); + } + } + *output = result; + } + + static bool Deserialize(Data* input, + UserType* output, + SerializationContext* context) { + if (!input) + return CallSetToNullIfExists(output); + + std::vector keys; + std::vector values; + + if (!KeyArraySerializer::DeserializeElements(input->keys.Get(), &keys, + context) || + !ValueArraySerializer::DeserializeElements(input->values.Get(), &values, + context)) { + return false; + } + + DCHECK_EQ(keys.size(), values.size()); + size_t size = keys.size(); + Traits::SetToEmpty(output); + + for (size_t i = 0; i < size; ++i) { + if (!Traits::Insert(*output, std::move(keys[i]), std::move(values[i]))) + return false; + } + return true; + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/may_auto_lock.h b/mojo/public/cpp/bindings/lib/may_auto_lock.h new file mode 100644 index 0000000000000000000000000000000000000000..06091fee90c91dcc8ee7896aa362e69c67acc917 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/may_auto_lock.h @@ -0,0 +1,62 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_MAY_AUTO_LOCK_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAY_AUTO_LOCK_H_ + +#include "base/macros.h" +#include "base/optional.h" +#include "base/synchronization/lock.h" + +namespace mojo { +namespace internal { + +// Similar to base::AutoLock, except that it does nothing if |lock| passed into +// the constructor is null. +class MayAutoLock { + public: + explicit MayAutoLock(base::Optional* lock) + : lock_(lock->has_value() ? &lock->value() : nullptr) { + if (lock_) + lock_->Acquire(); + } + + ~MayAutoLock() { + if (lock_) { + lock_->AssertAcquired(); + lock_->Release(); + } + } + + private: + base::Lock* lock_; + DISALLOW_COPY_AND_ASSIGN(MayAutoLock); +}; + +// Similar to base::AutoUnlock, except that it does nothing if |lock| passed +// into the constructor is null. +class MayAutoUnlock { + public: + explicit MayAutoUnlock(base::Optional* lock) + : lock_(lock->has_value() ? &lock->value() : nullptr) { + if (lock_) { + lock_->AssertAcquired(); + lock_->Release(); + } + } + + ~MayAutoUnlock() { + if (lock_) + lock_->Acquire(); + } + + private: + base::Lock* lock_; + DISALLOW_COPY_AND_ASSIGN(MayAutoUnlock); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAY_AUTO_LOCK_H_ diff --git a/mojo/public/cpp/bindings/lib/message.cc b/mojo/public/cpp/bindings/lib/message.cc new file mode 100644 index 0000000000000000000000000000000000000000..e5f38081176f5843c59669fcd5ba64b001cc2383 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message.cc @@ -0,0 +1,332 @@ +// Copyright 2013 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. + +#include "mojo/public/cpp/bindings/message.h" + +#include +#include +#include + +#include +#include + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_local.h" +#include "mojo/public/cpp/bindings/associated_group_controller.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" + +namespace mojo { + +namespace { + +base::LazyInstance>:: + DestructorAtExit g_tls_message_dispatch_context = LAZY_INSTANCE_INITIALIZER; + +base::LazyInstance>:: + DestructorAtExit g_tls_sync_response_context = LAZY_INSTANCE_INITIALIZER; + +void DoNotifyBadMessage(Message message, const std::string& error) { + message.NotifyBadMessage(error); +} + +} // namespace + +Message::Message() { +} + +Message::Message(Message&& other) + : buffer_(std::move(other.buffer_)), + handles_(std::move(other.handles_)), + associated_endpoint_handles_( + std::move(other.associated_endpoint_handles_)) {} + +Message::~Message() { + CloseHandles(); +} + +Message& Message::operator=(Message&& other) { + Reset(); + std::swap(other.buffer_, buffer_); + std::swap(other.handles_, handles_); + std::swap(other.associated_endpoint_handles_, associated_endpoint_handles_); + return *this; +} + +void Message::Reset() { + CloseHandles(); + handles_.clear(); + associated_endpoint_handles_.clear(); + buffer_.reset(); +} + +void Message::Initialize(size_t capacity, bool zero_initialized) { + DCHECK(!buffer_); + buffer_.reset(new internal::MessageBuffer(capacity, zero_initialized)); +} + +void Message::InitializeFromMojoMessage(ScopedMessageHandle message, + uint32_t num_bytes, + std::vector* handles) { + DCHECK(!buffer_); + buffer_.reset(new internal::MessageBuffer(std::move(message), num_bytes)); + handles_.swap(*handles); +} + +const uint8_t* Message::payload() const { + if (version() < 2) + return data() + header()->num_bytes; + + return static_cast(header_v2()->payload.Get()); +} + +uint32_t Message::payload_num_bytes() const { + DCHECK_GE(data_num_bytes(), header()->num_bytes); + size_t num_bytes; + if (version() < 2) { + num_bytes = data_num_bytes() - header()->num_bytes; + } else { + auto payload = reinterpret_cast(header_v2()->payload.Get()); + if (!payload) { + num_bytes = 0; + } else { + auto payload_end = + reinterpret_cast(header_v2()->payload_interface_ids.Get()); + if (!payload_end) + payload_end = reinterpret_cast(data() + data_num_bytes()); + DCHECK_GE(payload_end, payload); + num_bytes = payload_end - payload; + } + } + DCHECK_LE(num_bytes, std::numeric_limits::max()); + return static_cast(num_bytes); +} + +uint32_t Message::payload_num_interface_ids() const { + auto* array_pointer = + version() < 2 ? nullptr : header_v2()->payload_interface_ids.Get(); + return array_pointer ? static_cast(array_pointer->size()) : 0; +} + +const uint32_t* Message::payload_interface_ids() const { + auto* array_pointer = + version() < 2 ? nullptr : header_v2()->payload_interface_ids.Get(); + return array_pointer ? array_pointer->storage() : nullptr; +} + +ScopedMessageHandle Message::TakeMojoMessage() { + // If there are associated endpoints transferred, + // SerializeAssociatedEndpointHandles() must be called before this method. + DCHECK(associated_endpoint_handles_.empty()); + + if (handles_.empty()) // Fast path for the common case: No handles. + return buffer_->TakeMessage(); + + // Allocate a new message with space for the handles, then copy the buffer + // contents into it. + // + // TODO(rockot): We could avoid this copy by extending GetSerializedSize() + // behavior to collect handles. It's unoptimized for now because it's much + // more common to have messages with no handles. + ScopedMessageHandle new_message; + MojoResult rv = AllocMessage( + data_num_bytes(), + handles_.empty() ? nullptr + : reinterpret_cast(handles_.data()), + handles_.size(), + MOJO_ALLOC_MESSAGE_FLAG_NONE, + &new_message); + CHECK_EQ(rv, MOJO_RESULT_OK); + handles_.clear(); + + void* new_buffer = nullptr; + rv = GetMessageBuffer(new_message.get(), &new_buffer); + CHECK_EQ(rv, MOJO_RESULT_OK); + + memcpy(new_buffer, data(), data_num_bytes()); + buffer_.reset(); + + return new_message; +} + +void Message::NotifyBadMessage(const std::string& error) { + DCHECK(buffer_); + buffer_->NotifyBadMessage(error); +} + +void Message::CloseHandles() { + for (std::vector::iterator it = handles_.begin(); + it != handles_.end(); ++it) { + if (it->is_valid()) + CloseRaw(*it); + } +} + +void Message::SerializeAssociatedEndpointHandles( + AssociatedGroupController* group_controller) { + if (associated_endpoint_handles_.empty()) + return; + + DCHECK_GE(version(), 2u); + DCHECK(header_v2()->payload_interface_ids.is_null()); + + size_t size = associated_endpoint_handles_.size(); + auto* data = internal::Array_Data::New(size, buffer()); + header_v2()->payload_interface_ids.Set(data); + + for (size_t i = 0; i < size; ++i) { + ScopedInterfaceEndpointHandle& handle = associated_endpoint_handles_[i]; + + DCHECK(handle.pending_association()); + data->storage()[i] = + group_controller->AssociateInterface(std::move(handle)); + } + associated_endpoint_handles_.clear(); +} + +bool Message::DeserializeAssociatedEndpointHandles( + AssociatedGroupController* group_controller) { + associated_endpoint_handles_.clear(); + + uint32_t num_ids = payload_num_interface_ids(); + if (num_ids == 0) + return true; + + associated_endpoint_handles_.reserve(num_ids); + uint32_t* ids = header_v2()->payload_interface_ids.Get()->storage(); + bool result = true; + for (uint32_t i = 0; i < num_ids; ++i) { + auto handle = group_controller->CreateLocalEndpointHandle(ids[i]); + if (IsValidInterfaceId(ids[i]) && !handle.is_valid()) { + // |ids[i]| itself is valid but handle creation failed. In that case, mark + // deserialization as failed but continue to deserialize the rest of + // handles. + result = false; + } + + associated_endpoint_handles_.push_back(std::move(handle)); + ids[i] = kInvalidInterfaceId; + } + return result; +} + +PassThroughFilter::PassThroughFilter() {} + +PassThroughFilter::~PassThroughFilter() {} + +bool PassThroughFilter::Accept(Message* message) { return true; } + +SyncMessageResponseContext::SyncMessageResponseContext() + : outer_context_(current()) { + g_tls_sync_response_context.Get().Set(this); +} + +SyncMessageResponseContext::~SyncMessageResponseContext() { + DCHECK_EQ(current(), this); + g_tls_sync_response_context.Get().Set(outer_context_); +} + +// static +SyncMessageResponseContext* SyncMessageResponseContext::current() { + return g_tls_sync_response_context.Get().Get(); +} + +void SyncMessageResponseContext::ReportBadMessage(const std::string& error) { + GetBadMessageCallback().Run(error); +} + +const ReportBadMessageCallback& +SyncMessageResponseContext::GetBadMessageCallback() { + if (bad_message_callback_.is_null()) { + bad_message_callback_ = + base::Bind(&DoNotifyBadMessage, base::Passed(&response_)); + } + return bad_message_callback_; +} + +MojoResult ReadMessage(MessagePipeHandle handle, Message* message) { + MojoResult rv; + + std::vector handles; + ScopedMessageHandle mojo_message; + uint32_t num_bytes = 0, num_handles = 0; + rv = ReadMessageNew(handle, + &mojo_message, + &num_bytes, + nullptr, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + if (rv == MOJO_RESULT_RESOURCE_EXHAUSTED) { + DCHECK_GT(num_handles, 0u); + handles.resize(num_handles); + rv = ReadMessageNew(handle, + &mojo_message, + &num_bytes, + reinterpret_cast(handles.data()), + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + } + + if (rv != MOJO_RESULT_OK) + return rv; + + message->InitializeFromMojoMessage( + std::move(mojo_message), num_bytes, &handles); + return MOJO_RESULT_OK; +} + +void ReportBadMessage(const std::string& error) { + internal::MessageDispatchContext* context = + internal::MessageDispatchContext::current(); + DCHECK(context); + context->GetBadMessageCallback().Run(error); +} + +ReportBadMessageCallback GetBadMessageCallback() { + internal::MessageDispatchContext* context = + internal::MessageDispatchContext::current(); + DCHECK(context); + return context->GetBadMessageCallback(); +} + +namespace internal { + +MessageHeaderV2::MessageHeaderV2() = default; + +MessageDispatchContext::MessageDispatchContext(Message* message) + : outer_context_(current()), message_(message) { + g_tls_message_dispatch_context.Get().Set(this); +} + +MessageDispatchContext::~MessageDispatchContext() { + DCHECK_EQ(current(), this); + g_tls_message_dispatch_context.Get().Set(outer_context_); +} + +// static +MessageDispatchContext* MessageDispatchContext::current() { + return g_tls_message_dispatch_context.Get().Get(); +} + +const ReportBadMessageCallback& +MessageDispatchContext::GetBadMessageCallback() { + if (bad_message_callback_.is_null()) { + bad_message_callback_ = + base::Bind(&DoNotifyBadMessage, base::Passed(message_)); + } + return bad_message_callback_; +} + +// static +void SyncMessageResponseSetup::SetCurrentSyncResponseMessage(Message* message) { + SyncMessageResponseContext* context = SyncMessageResponseContext::current(); + if (context) + context->response_ = std::move(*message); +} + +} // namespace internal + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_buffer.cc b/mojo/public/cpp/bindings/lib/message_buffer.cc new file mode 100644 index 0000000000000000000000000000000000000000..cc12ef6e31c9145f97768371834f4e2a494065dd --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_buffer.cc @@ -0,0 +1,52 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/lib/message_buffer.h" + +#include + +#include "mojo/public/cpp/bindings/lib/serialization_util.h" + +namespace mojo { +namespace internal { + +MessageBuffer::MessageBuffer(size_t capacity, bool zero_initialized) { + DCHECK_LE(capacity, std::numeric_limits::max()); + + MojoResult rv = AllocMessage(capacity, nullptr, 0, + MOJO_ALLOC_MESSAGE_FLAG_NONE, &message_); + CHECK_EQ(rv, MOJO_RESULT_OK); + + void* buffer = nullptr; + if (capacity != 0) { + rv = GetMessageBuffer(message_.get(), &buffer); + CHECK_EQ(rv, MOJO_RESULT_OK); + + if (zero_initialized) + memset(buffer, 0, capacity); + } + Initialize(buffer, capacity); +} + +MessageBuffer::MessageBuffer(ScopedMessageHandle message, uint32_t num_bytes) { + message_ = std::move(message); + + void* buffer = nullptr; + if (num_bytes != 0) { + MojoResult rv = GetMessageBuffer(message_.get(), &buffer); + CHECK_EQ(rv, MOJO_RESULT_OK); + } + Initialize(buffer, num_bytes); +} + +MessageBuffer::~MessageBuffer() {} + +void MessageBuffer::NotifyBadMessage(const std::string& error) { + DCHECK(message_.is_valid()); + MojoResult result = mojo::NotifyBadMessage(message_.get(), error); + DCHECK_EQ(result, MOJO_RESULT_OK); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_buffer.h b/mojo/public/cpp/bindings/lib/message_buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..96d5140f7741a30e7e3de0374426a4c324be325a --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_buffer.h @@ -0,0 +1,43 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_LIB_MESSAGE_BUFFER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_LIB_MESSAGE_BUFFER_H_ + +#include + +#include + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/system/message.h" + +namespace mojo { +namespace internal { + +// A fixed-size Buffer using a Mojo message object for storage. +class MessageBuffer : public Buffer { + public: + // Initializes this buffer to carry a fixed byte capacity and no handles. + MessageBuffer(size_t capacity, bool zero_initialized); + + // Initializes this buffer from an existing Mojo MessageHandle. + MessageBuffer(ScopedMessageHandle message, uint32_t num_bytes); + + ~MessageBuffer(); + + ScopedMessageHandle TakeMessage() { return std::move(message_); } + + void NotifyBadMessage(const std::string& error); + + private: + ScopedMessageHandle message_; + + DISALLOW_COPY_AND_ASSIGN(MessageBuffer); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_LIB_MESSAGE_BUFFER_H_ diff --git a/mojo/public/cpp/bindings/lib/message_builder.cc b/mojo/public/cpp/bindings/lib/message_builder.cc new file mode 100644 index 0000000000000000000000000000000000000000..6806a73213c6cbd0b184aad3a397832726525327 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_builder.cc @@ -0,0 +1,69 @@ +// Copyright 2013 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. + +#include "mojo/public/cpp/bindings/lib/message_builder.h" + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/bindings/lib/message_internal.h" + +namespace mojo { +namespace internal { + +template +void Allocate(Buffer* buf, Header** header) { + *header = static_cast(buf->Allocate(sizeof(Header))); + (*header)->num_bytes = sizeof(Header); +} + +MessageBuilder::MessageBuilder(uint32_t name, + uint32_t flags, + size_t payload_size, + size_t payload_interface_id_count) { + if (payload_interface_id_count > 0) { + // Version 2 + InitializeMessage( + sizeof(MessageHeaderV2) + Align(payload_size) + + ArrayDataTraits::GetStorageSize( + static_cast(payload_interface_id_count))); + + MessageHeaderV2* header; + Allocate(message_.buffer(), &header); + header->version = 2; + header->name = name; + header->flags = flags; + // The payload immediately follows the header. + header->payload.Set(header + 1); + } else if (flags & + (Message::kFlagExpectsResponse | Message::kFlagIsResponse)) { + // Version 1 + InitializeMessage(sizeof(MessageHeaderV1) + payload_size); + + MessageHeaderV1* header; + Allocate(message_.buffer(), &header); + header->version = 1; + header->name = name; + header->flags = flags; + } else { + InitializeMessage(sizeof(MessageHeader) + payload_size); + + MessageHeader* header; + Allocate(message_.buffer(), &header); + header->version = 0; + header->name = name; + header->flags = flags; + } +} + +MessageBuilder::~MessageBuilder() { +} + +void MessageBuilder::InitializeMessage(size_t size) { + message_.Initialize(static_cast(Align(size)), + true /* zero_initialized */); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_builder.h b/mojo/public/cpp/bindings/lib/message_builder.h new file mode 100644 index 0000000000000000000000000000000000000000..8a4d5c46908897378307b566fed074965d1c4705 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_builder.h @@ -0,0 +1,45 @@ +// Copyright 2013 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ + +#include +#include + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { + +class Message; + +namespace internal { + +class Buffer; + +class MOJO_CPP_BINDINGS_EXPORT MessageBuilder { + public: + MessageBuilder(uint32_t name, + uint32_t flags, + size_t payload_size, + size_t payload_interface_id_count); + ~MessageBuilder(); + + Buffer* buffer() { return message_.buffer(); } + Message* message() { return &message_; } + + private: + void InitializeMessage(size_t size); + + Message message_; + + DISALLOW_COPY_AND_ASSIGN(MessageBuilder); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ diff --git a/mojo/public/cpp/bindings/lib/message_header_validator.cc b/mojo/public/cpp/bindings/lib/message_header_validator.cc new file mode 100644 index 0000000000000000000000000000000000000000..9f8c6278c07d0c520ad3ef11e3191d65307e4982 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_header_validator.cc @@ -0,0 +1,133 @@ +// Copyright 2014 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. + +#include "mojo/public/cpp/bindings/message_header_validator.h" + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" + +namespace mojo { +namespace { + +// TODO(yzshen): Define a mojom struct for message header and use the generated +// validation and data view code. +bool IsValidMessageHeader(const internal::MessageHeader* header, + internal::ValidationContext* validation_context) { + // NOTE: Our goal is to preserve support for future extension of the message + // header. If we encounter fields we do not understand, we must ignore them. + + // Extra validation of the struct header: + do { + if (header->version == 0) { + if (header->num_bytes == sizeof(internal::MessageHeader)) + break; + } else if (header->version == 1) { + if (header->num_bytes == sizeof(internal::MessageHeaderV1)) + break; + } else if (header->version == 2) { + if (header->num_bytes == sizeof(internal::MessageHeaderV2)) + break; + } else if (header->version > 2) { + if (header->num_bytes >= sizeof(internal::MessageHeaderV2)) + break; + } + internal::ReportValidationError( + validation_context, + internal::VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } while (false); + + // Validate flags (allow unknown bits): + + // These flags require a RequestID. + constexpr uint32_t kRequestIdFlags = + Message::kFlagExpectsResponse | Message::kFlagIsResponse; + if (header->version == 0 && (header->flags & kRequestIdFlags)) { + internal::ReportValidationError( + validation_context, + internal::VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID); + return false; + } + + // These flags are mutually exclusive. + if ((header->flags & kRequestIdFlags) == kRequestIdFlags) { + internal::ReportValidationError( + validation_context, + internal::VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS); + return false; + } + + if (header->version < 2) + return true; + + auto* header_v2 = static_cast(header); + // For the payload pointer: + // - Check that the pointer can be safely decoded. + // - Claim one byte that the pointer points to. It makes sure not only the + // address is within the message, but also the address precedes the array + // storing interface IDs (which is important for safely calculating the + // payload size). + // - Validation of the payload contents will be done separately based on the + // payload type. + if (!header_v2->payload.is_null() && + (!internal::ValidatePointer(header_v2->payload, validation_context) || + !validation_context->ClaimMemory(header_v2->payload.Get(), 1))) { + return false; + } + + const internal::ContainerValidateParams validate_params(0, false, nullptr); + if (!internal::ValidateContainer(header_v2->payload_interface_ids, + validation_context, &validate_params)) { + return false; + } + + if (!header_v2->payload_interface_ids.is_null()) { + size_t num_ids = header_v2->payload_interface_ids.Get()->size(); + const uint32_t* ids = header_v2->payload_interface_ids.Get()->storage(); + for (size_t i = 0; i < num_ids; ++i) { + if (!IsValidInterfaceId(ids[i]) || IsMasterInterfaceId(ids[i])) { + internal::ReportValidationError( + validation_context, + internal::VALIDATION_ERROR_ILLEGAL_INTERFACE_ID); + return false; + } + } + } + + return true; +} + +} // namespace + +MessageHeaderValidator::MessageHeaderValidator() + : MessageHeaderValidator("MessageHeaderValidator") {} + +MessageHeaderValidator::MessageHeaderValidator(const std::string& description) + : description_(description) { +} + +void MessageHeaderValidator::SetDescription(const std::string& description) { + description_ = description; +} + +bool MessageHeaderValidator::Accept(Message* message) { + // Pass 0 as number of handles and associated endpoint handles because we + // don't expect any in the header, even if |message| contains handles. + internal::ValidationContext validation_context( + message->data(), message->data_num_bytes(), 0, 0, message, description_); + + if (!internal::ValidateStructHeaderAndClaimMemory(message->data(), + &validation_context)) + return false; + + if (!IsValidMessageHeader(message->header(), &validation_context)) + return false; + + return true; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_internal.h b/mojo/public/cpp/bindings/lib/message_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..6693198f8111a408e6e40511e32001439c46d89c --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_internal.h @@ -0,0 +1,82 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ + +#include + +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" + +namespace mojo { + +class Message; + +namespace internal { + +template +class Array_Data; + +#pragma pack(push, 1) + +struct MessageHeader : internal::StructHeader { + // Interface ID for identifying multiple interfaces running on the same + // message pipe. + uint32_t interface_id; + // Message name, which is scoped to the interface that the message belongs to. + uint32_t name; + // 0 or either of the enum values defined above. + uint32_t flags; + // Unused padding to make the struct size a multiple of 8 bytes. + uint32_t padding; +}; +static_assert(sizeof(MessageHeader) == 24, "Bad sizeof(MessageHeader)"); + +struct MessageHeaderV1 : MessageHeader { + // Only used if either kFlagExpectsResponse or kFlagIsResponse is set in + // order to match responses with corresponding requests. + uint64_t request_id; +}; +static_assert(sizeof(MessageHeaderV1) == 32, "Bad sizeof(MessageHeaderV1)"); + +struct MessageHeaderV2 : MessageHeaderV1 { + MessageHeaderV2(); + GenericPointer payload; + Pointer> payload_interface_ids; +}; +static_assert(sizeof(MessageHeaderV2) == 48, "Bad sizeof(MessageHeaderV2)"); + +#pragma pack(pop) + +class MOJO_CPP_BINDINGS_EXPORT MessageDispatchContext { + public: + explicit MessageDispatchContext(Message* message); + ~MessageDispatchContext(); + + static MessageDispatchContext* current(); + + const base::Callback& GetBadMessageCallback(); + + private: + MessageDispatchContext* outer_context_; + Message* message_; + base::Callback bad_message_callback_; + + DISALLOW_COPY_AND_ASSIGN(MessageDispatchContext); +}; + +class MOJO_CPP_BINDINGS_EXPORT SyncMessageResponseSetup { + public: + static void SetCurrentSyncResponseMessage(Message* message); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.cc b/mojo/public/cpp/bindings/lib/multiplex_router.cc new file mode 100644 index 0000000000000000000000000000000000000000..ff7c678289175ea8f920f5f02186d4c215da1f1a --- /dev/null +++ b/mojo/public/cpp/bindings/lib/multiplex_router.cc @@ -0,0 +1,960 @@ +// 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. + +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" + +#include + +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/interface_endpoint_controller.h" +#include "mojo/public/cpp/bindings/lib/may_auto_lock.h" +#include "mojo/public/cpp/bindings/sync_event_watcher.h" + +namespace mojo { +namespace internal { + +// InterfaceEndpoint stores the information of an interface endpoint registered +// with the router. +// No one other than the router's |endpoints_| and |tasks_| should hold refs to +// this object. +class MultiplexRouter::InterfaceEndpoint + : public base::RefCountedThreadSafe, + public InterfaceEndpointController { + public: + InterfaceEndpoint(MultiplexRouter* router, InterfaceId id) + : router_(router), + id_(id), + closed_(false), + peer_closed_(false), + handle_created_(false), + client_(nullptr) {} + + // --------------------------------------------------------------------------- + // The following public methods are safe to call from any threads without + // locking. + + InterfaceId id() const { return id_; } + + // --------------------------------------------------------------------------- + // The following public methods are called under the router's lock. + + bool closed() const { return closed_; } + void set_closed() { + router_->AssertLockAcquired(); + closed_ = true; + } + + bool peer_closed() const { return peer_closed_; } + void set_peer_closed() { + router_->AssertLockAcquired(); + peer_closed_ = true; + } + + bool handle_created() const { return handle_created_; } + void set_handle_created() { + router_->AssertLockAcquired(); + handle_created_ = true; + } + + const base::Optional& disconnect_reason() const { + return disconnect_reason_; + } + void set_disconnect_reason( + const base::Optional& disconnect_reason) { + router_->AssertLockAcquired(); + disconnect_reason_ = disconnect_reason; + } + + base::SingleThreadTaskRunner* task_runner() const { + return task_runner_.get(); + } + + InterfaceEndpointClient* client() const { return client_; } + + void AttachClient(InterfaceEndpointClient* client, + scoped_refptr runner) { + router_->AssertLockAcquired(); + DCHECK(!client_); + DCHECK(!closed_); + DCHECK(runner->BelongsToCurrentThread()); + + task_runner_ = std::move(runner); + client_ = client; + } + + // This method must be called on the same thread as the corresponding + // AttachClient() call. + void DetachClient() { + router_->AssertLockAcquired(); + DCHECK(client_); + DCHECK(task_runner_->BelongsToCurrentThread()); + DCHECK(!closed_); + + task_runner_ = nullptr; + client_ = nullptr; + sync_watcher_.reset(); + } + + void SignalSyncMessageEvent() { + router_->AssertLockAcquired(); + if (sync_message_event_signaled_) + return; + sync_message_event_signaled_ = true; + if (sync_message_event_) + sync_message_event_->Signal(); + } + + void ResetSyncMessageSignal() { + router_->AssertLockAcquired(); + if (!sync_message_event_signaled_) + return; + sync_message_event_signaled_ = false; + if (sync_message_event_) + sync_message_event_->Reset(); + } + + // --------------------------------------------------------------------------- + // The following public methods (i.e., InterfaceEndpointController + // implementation) are called by the client on the same thread as the + // AttachClient() call. They are called outside of the router's lock. + + bool SendMessage(Message* message) override { + DCHECK(task_runner_->BelongsToCurrentThread()); + message->set_interface_id(id_); + return router_->connector_.Accept(message); + } + + void AllowWokenUpBySyncWatchOnSameThread() override { + DCHECK(task_runner_->BelongsToCurrentThread()); + + EnsureSyncWatcherExists(); + sync_watcher_->AllowWokenUpBySyncWatchOnSameThread(); + } + + bool SyncWatch(const bool* should_stop) override { + DCHECK(task_runner_->BelongsToCurrentThread()); + + EnsureSyncWatcherExists(); + return sync_watcher_->SyncWatch(should_stop); + } + + private: + friend class base::RefCountedThreadSafe; + + ~InterfaceEndpoint() override { + router_->AssertLockAcquired(); + + DCHECK(!client_); + DCHECK(closed_); + DCHECK(peer_closed_); + DCHECK(!sync_watcher_); + } + + void OnSyncEventSignaled() { + DCHECK(task_runner_->BelongsToCurrentThread()); + scoped_refptr router_protector(router_); + + MayAutoLock locker(&router_->lock_); + scoped_refptr self_protector(this); + + bool more_to_process = router_->ProcessFirstSyncMessageForEndpoint(id_); + + if (!more_to_process) + ResetSyncMessageSignal(); + + // Currently there are no queued sync messages and the peer has closed so + // there won't be incoming sync messages in the future. + if (!more_to_process && peer_closed_) { + // If a SyncWatch() call (or multiple ones) of this interface endpoint is + // on the call stack, resetting the sync watcher will allow it to exit + // when the call stack unwinds to that frame. + sync_watcher_.reset(); + } + } + + void EnsureSyncWatcherExists() { + DCHECK(task_runner_->BelongsToCurrentThread()); + if (sync_watcher_) + return; + + { + MayAutoLock locker(&router_->lock_); + if (!sync_message_event_) { + sync_message_event_.emplace( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + if (sync_message_event_signaled_) + sync_message_event_->Signal(); + } + } + sync_watcher_.reset( + new SyncEventWatcher(&sync_message_event_.value(), + base::Bind(&InterfaceEndpoint::OnSyncEventSignaled, + base::Unretained(this)))); + } + + // --------------------------------------------------------------------------- + // The following members are safe to access from any threads. + + MultiplexRouter* const router_; + const InterfaceId id_; + + // --------------------------------------------------------------------------- + // The following members are accessed under the router's lock. + + // Whether the endpoint has been closed. + bool closed_; + // Whether the peer endpoint has been closed. + bool peer_closed_; + + // Whether there is already a ScopedInterfaceEndpointHandle created for this + // endpoint. + bool handle_created_; + + base::Optional disconnect_reason_; + + // The task runner on which |client_|'s methods can be called. + scoped_refptr task_runner_; + // Not owned. It is null if no client is attached to this endpoint. + InterfaceEndpointClient* client_; + + // An event used to signal that sync messages are available. The event is + // initialized under the router's lock and remains unchanged afterwards. It + // may be accessed outside of the router's lock later. + base::Optional sync_message_event_; + bool sync_message_event_signaled_ = false; + + // --------------------------------------------------------------------------- + // The following members are only valid while a client is attached. They are + // used exclusively on the client's thread. They may be accessed outside of + // the router's lock. + + std::unique_ptr sync_watcher_; + + DISALLOW_COPY_AND_ASSIGN(InterfaceEndpoint); +}; + +// MessageWrapper objects are always destroyed under the router's lock. On +// destruction, if the message it wrappers contains +// ScopedInterfaceEndpointHandles (which cannot be destructed under the +// router's lock), the wrapper unlocks to clean them up. +class MultiplexRouter::MessageWrapper { + public: + MessageWrapper() = default; + + MessageWrapper(MultiplexRouter* router, Message message) + : router_(router), value_(std::move(message)) {} + + MessageWrapper(MessageWrapper&& other) + : router_(other.router_), value_(std::move(other.value_)) {} + + ~MessageWrapper() { + if (value_.associated_endpoint_handles()->empty()) + return; + + router_->AssertLockAcquired(); + { + MayAutoUnlock unlocker(&router_->lock_); + value_.mutable_associated_endpoint_handles()->clear(); + } + } + + MessageWrapper& operator=(MessageWrapper&& other) { + router_ = other.router_; + value_ = std::move(other.value_); + return *this; + } + + Message& value() { return value_; } + + private: + MultiplexRouter* router_ = nullptr; + Message value_; + + DISALLOW_COPY_AND_ASSIGN(MessageWrapper); +}; + +struct MultiplexRouter::Task { + public: + // Doesn't take ownership of |message| but takes its contents. + static std::unique_ptr CreateMessageTask( + MessageWrapper message_wrapper) { + Task* task = new Task(MESSAGE); + task->message_wrapper = std::move(message_wrapper); + return base::WrapUnique(task); + } + static std::unique_ptr CreateNotifyErrorTask( + InterfaceEndpoint* endpoint) { + Task* task = new Task(NOTIFY_ERROR); + task->endpoint_to_notify = endpoint; + return base::WrapUnique(task); + } + + ~Task() {} + + bool IsMessageTask() const { return type == MESSAGE; } + bool IsNotifyErrorTask() const { return type == NOTIFY_ERROR; } + + MessageWrapper message_wrapper; + scoped_refptr endpoint_to_notify; + + enum Type { MESSAGE, NOTIFY_ERROR }; + Type type; + + private: + explicit Task(Type in_type) : type(in_type) {} + + DISALLOW_COPY_AND_ASSIGN(Task); +}; + +MultiplexRouter::MultiplexRouter( + ScopedMessagePipeHandle message_pipe, + Config config, + bool set_interface_id_namesapce_bit, + scoped_refptr runner) + : set_interface_id_namespace_bit_(set_interface_id_namesapce_bit), + task_runner_(runner), + header_validator_(nullptr), + filters_(this), + connector_(std::move(message_pipe), + config == MULTI_INTERFACE ? Connector::MULTI_THREADED_SEND + : Connector::SINGLE_THREADED_SEND, + std::move(runner)), + control_message_handler_(this), + control_message_proxy_(&connector_), + next_interface_id_value_(1), + posted_to_process_tasks_(false), + encountered_error_(false), + paused_(false), + testing_mode_(false) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + if (config == MULTI_INTERFACE) + lock_.emplace(); + + if (config == SINGLE_INTERFACE_WITH_SYNC_METHODS || + config == MULTI_INTERFACE) { + // Always participate in sync handle watching in multi-interface mode, + // because even if it doesn't expect sync requests during sync handle + // watching, it may still need to dispatch messages to associated endpoints + // on a different thread. + connector_.AllowWokenUpBySyncWatchOnSameThread(); + } + connector_.set_incoming_receiver(&filters_); + connector_.set_connection_error_handler( + base::Bind(&MultiplexRouter::OnPipeConnectionError, + base::Unretained(this))); + + std::unique_ptr header_validator = + base::MakeUnique(); + header_validator_ = header_validator.get(); + filters_.Append(std::move(header_validator)); +} + +MultiplexRouter::~MultiplexRouter() { + MayAutoLock locker(&lock_); + + sync_message_tasks_.clear(); + tasks_.clear(); + + for (auto iter = endpoints_.begin(); iter != endpoints_.end();) { + InterfaceEndpoint* endpoint = iter->second.get(); + // Increment the iterator before calling UpdateEndpointStateMayRemove() + // because it may remove the corresponding value from the map. + ++iter; + + if (!endpoint->closed()) { + // This happens when a NotifyPeerEndpointClosed message been received, but + // the interface ID hasn't been used to create local endpoint handle. + DCHECK(!endpoint->client()); + DCHECK(endpoint->peer_closed()); + UpdateEndpointStateMayRemove(endpoint, ENDPOINT_CLOSED); + } else { + UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED); + } + } + + DCHECK(endpoints_.empty()); +} + +void MultiplexRouter::SetMasterInterfaceName(const char* name) { + DCHECK(thread_checker_.CalledOnValidThread()); + header_validator_->SetDescription( + std::string(name) + " [master] MessageHeaderValidator"); + control_message_handler_.SetDescription( + std::string(name) + " [master] PipeControlMessageHandler"); + connector_.SetWatcherHeapProfilerTag(name); +} + +InterfaceId MultiplexRouter::AssociateInterface( + ScopedInterfaceEndpointHandle handle_to_send) { + if (!handle_to_send.pending_association()) + return kInvalidInterfaceId; + + uint32_t id = 0; + { + MayAutoLock locker(&lock_); + do { + if (next_interface_id_value_ >= kInterfaceIdNamespaceMask) + next_interface_id_value_ = 1; + id = next_interface_id_value_++; + if (set_interface_id_namespace_bit_) + id |= kInterfaceIdNamespaceMask; + } while (base::ContainsKey(endpoints_, id)); + + InterfaceEndpoint* endpoint = new InterfaceEndpoint(this, id); + endpoints_[id] = endpoint; + if (encountered_error_) + UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED); + endpoint->set_handle_created(); + } + + if (!NotifyAssociation(&handle_to_send, id)) { + // The peer handle of |handle_to_send|, which is supposed to join this + // associated group, has been closed. + { + MayAutoLock locker(&lock_); + InterfaceEndpoint* endpoint = FindEndpoint(id); + if (endpoint) + UpdateEndpointStateMayRemove(endpoint, ENDPOINT_CLOSED); + } + + control_message_proxy_.NotifyPeerEndpointClosed( + id, handle_to_send.disconnect_reason()); + } + return id; +} + +ScopedInterfaceEndpointHandle MultiplexRouter::CreateLocalEndpointHandle( + InterfaceId id) { + if (!IsValidInterfaceId(id)) + return ScopedInterfaceEndpointHandle(); + + MayAutoLock locker(&lock_); + bool inserted = false; + InterfaceEndpoint* endpoint = FindOrInsertEndpoint(id, &inserted); + if (inserted) { + DCHECK(!endpoint->handle_created()); + + if (encountered_error_) + UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED); + } else { + // If the endpoint already exist, it is because we have received a + // notification that the peer endpoint has closed. + CHECK(!endpoint->closed()); + CHECK(endpoint->peer_closed()); + + if (endpoint->handle_created()) + return ScopedInterfaceEndpointHandle(); + } + + endpoint->set_handle_created(); + return CreateScopedInterfaceEndpointHandle(id); +} + +void MultiplexRouter::CloseEndpointHandle( + InterfaceId id, + const base::Optional& reason) { + if (!IsValidInterfaceId(id)) + return; + + MayAutoLock locker(&lock_); + DCHECK(base::ContainsKey(endpoints_, id)); + InterfaceEndpoint* endpoint = endpoints_[id].get(); + DCHECK(!endpoint->client()); + DCHECK(!endpoint->closed()); + UpdateEndpointStateMayRemove(endpoint, ENDPOINT_CLOSED); + + if (!IsMasterInterfaceId(id) || reason) { + MayAutoUnlock unlocker(&lock_); + control_message_proxy_.NotifyPeerEndpointClosed(id, reason); + } + + ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr); +} + +InterfaceEndpointController* MultiplexRouter::AttachEndpointClient( + const ScopedInterfaceEndpointHandle& handle, + InterfaceEndpointClient* client, + scoped_refptr runner) { + const InterfaceId id = handle.id(); + + DCHECK(IsValidInterfaceId(id)); + DCHECK(client); + + MayAutoLock locker(&lock_); + DCHECK(base::ContainsKey(endpoints_, id)); + + InterfaceEndpoint* endpoint = endpoints_[id].get(); + endpoint->AttachClient(client, std::move(runner)); + + if (endpoint->peer_closed()) + tasks_.push_back(Task::CreateNotifyErrorTask(endpoint)); + ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr); + + return endpoint; +} + +void MultiplexRouter::DetachEndpointClient( + const ScopedInterfaceEndpointHandle& handle) { + const InterfaceId id = handle.id(); + + DCHECK(IsValidInterfaceId(id)); + + MayAutoLock locker(&lock_); + DCHECK(base::ContainsKey(endpoints_, id)); + + InterfaceEndpoint* endpoint = endpoints_[id].get(); + endpoint->DetachClient(); +} + +void MultiplexRouter::RaiseError() { + if (task_runner_->BelongsToCurrentThread()) { + connector_.RaiseError(); + } else { + task_runner_->PostTask(FROM_HERE, + base::Bind(&MultiplexRouter::RaiseError, this)); + } +} + +void MultiplexRouter::CloseMessagePipe() { + DCHECK(thread_checker_.CalledOnValidThread()); + connector_.CloseMessagePipe(); + // CloseMessagePipe() above won't trigger connection error handler. + // Explicitly call OnPipeConnectionError() so that associated endpoints will + // get notified. + OnPipeConnectionError(); +} + +void MultiplexRouter::PauseIncomingMethodCallProcessing() { + DCHECK(thread_checker_.CalledOnValidThread()); + connector_.PauseIncomingMethodCallProcessing(); + + MayAutoLock locker(&lock_); + paused_ = true; + + for (auto iter = endpoints_.begin(); iter != endpoints_.end(); ++iter) + iter->second->ResetSyncMessageSignal(); +} + +void MultiplexRouter::ResumeIncomingMethodCallProcessing() { + DCHECK(thread_checker_.CalledOnValidThread()); + connector_.ResumeIncomingMethodCallProcessing(); + + MayAutoLock locker(&lock_); + paused_ = false; + + for (auto iter = endpoints_.begin(); iter != endpoints_.end(); ++iter) { + auto sync_iter = sync_message_tasks_.find(iter->first); + if (iter->second->peer_closed() || + (sync_iter != sync_message_tasks_.end() && + !sync_iter->second.empty())) { + iter->second->SignalSyncMessageEvent(); + } + } + + ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr); +} + +bool MultiplexRouter::HasAssociatedEndpoints() const { + DCHECK(thread_checker_.CalledOnValidThread()); + MayAutoLock locker(&lock_); + + if (endpoints_.size() > 1) + return true; + if (endpoints_.size() == 0) + return false; + + return !base::ContainsKey(endpoints_, kMasterInterfaceId); +} + +void MultiplexRouter::EnableTestingMode() { + DCHECK(thread_checker_.CalledOnValidThread()); + MayAutoLock locker(&lock_); + + testing_mode_ = true; + connector_.set_enforce_errors_from_incoming_receiver(false); +} + +bool MultiplexRouter::Accept(Message* message) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!message->DeserializeAssociatedEndpointHandles(this)) + return false; + + scoped_refptr protector(this); + MayAutoLock locker(&lock_); + + DCHECK(!paused_); + + ClientCallBehavior client_call_behavior = + connector_.during_sync_handle_watcher_callback() + ? ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES + : ALLOW_DIRECT_CLIENT_CALLS; + + bool processed = + tasks_.empty() && ProcessIncomingMessage(message, client_call_behavior, + connector_.task_runner()); + + if (!processed) { + // Either the task queue is not empty or we cannot process the message + // directly. In both cases, there is no need to call ProcessTasks(). + tasks_.push_back( + Task::CreateMessageTask(MessageWrapper(this, std::move(*message)))); + Task* task = tasks_.back().get(); + + if (task->message_wrapper.value().has_flag(Message::kFlagIsSync)) { + InterfaceId id = task->message_wrapper.value().interface_id(); + sync_message_tasks_[id].push_back(task); + InterfaceEndpoint* endpoint = FindEndpoint(id); + if (endpoint) + endpoint->SignalSyncMessageEvent(); + } + } else if (!tasks_.empty()) { + // Processing the message may result in new tasks (for error notification) + // being added to the queue. In this case, we have to attempt to process the + // tasks. + ProcessTasks(client_call_behavior, connector_.task_runner()); + } + + // Always return true. If we see errors during message processing, we will + // explicitly call Connector::RaiseError() to disconnect the message pipe. + return true; +} + +bool MultiplexRouter::OnPeerAssociatedEndpointClosed( + InterfaceId id, + const base::Optional& reason) { + DCHECK(!IsMasterInterfaceId(id) || reason); + + MayAutoLock locker(&lock_); + InterfaceEndpoint* endpoint = FindOrInsertEndpoint(id, nullptr); + + if (reason) + endpoint->set_disconnect_reason(reason); + + // It is possible that this endpoint has been set as peer closed. That is + // because when the message pipe is closed, all the endpoints are updated with + // PEER_ENDPOINT_CLOSED. We continue to process remaining tasks in the queue, + // as long as there are refs keeping the router alive. If there is a + // PeerAssociatedEndpointClosedEvent control message in the queue, we will get + // here and see that the endpoint has been marked as peer closed. + if (!endpoint->peer_closed()) { + if (endpoint->client()) + tasks_.push_back(Task::CreateNotifyErrorTask(endpoint)); + UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED); + } + + // No need to trigger a ProcessTasks() because it is already on the stack. + + return true; +} + +void MultiplexRouter::OnPipeConnectionError() { + DCHECK(thread_checker_.CalledOnValidThread()); + + scoped_refptr protector(this); + MayAutoLock locker(&lock_); + + encountered_error_ = true; + + for (auto iter = endpoints_.begin(); iter != endpoints_.end();) { + InterfaceEndpoint* endpoint = iter->second.get(); + // Increment the iterator before calling UpdateEndpointStateMayRemove() + // because it may remove the corresponding value from the map. + ++iter; + + if (endpoint->client()) + tasks_.push_back(Task::CreateNotifyErrorTask(endpoint)); + + UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED); + } + + ProcessTasks(connector_.during_sync_handle_watcher_callback() + ? ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES + : ALLOW_DIRECT_CLIENT_CALLS, + connector_.task_runner()); +} + +void MultiplexRouter::ProcessTasks( + ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner) { + AssertLockAcquired(); + + if (posted_to_process_tasks_) + return; + + while (!tasks_.empty() && !paused_) { + std::unique_ptr task(std::move(tasks_.front())); + tasks_.pop_front(); + + InterfaceId id = kInvalidInterfaceId; + bool sync_message = + task->IsMessageTask() && !task->message_wrapper.value().IsNull() && + task->message_wrapper.value().has_flag(Message::kFlagIsSync); + if (sync_message) { + id = task->message_wrapper.value().interface_id(); + auto& sync_message_queue = sync_message_tasks_[id]; + DCHECK_EQ(task.get(), sync_message_queue.front()); + sync_message_queue.pop_front(); + } + + bool processed = + task->IsNotifyErrorTask() + ? ProcessNotifyErrorTask(task.get(), client_call_behavior, + current_task_runner) + : ProcessIncomingMessage(&task->message_wrapper.value(), + client_call_behavior, current_task_runner); + + if (!processed) { + if (sync_message) { + auto& sync_message_queue = sync_message_tasks_[id]; + sync_message_queue.push_front(task.get()); + } + tasks_.push_front(std::move(task)); + break; + } else { + if (sync_message) { + auto iter = sync_message_tasks_.find(id); + if (iter != sync_message_tasks_.end() && iter->second.empty()) + sync_message_tasks_.erase(iter); + } + } + } +} + +bool MultiplexRouter::ProcessFirstSyncMessageForEndpoint(InterfaceId id) { + AssertLockAcquired(); + + auto iter = sync_message_tasks_.find(id); + if (iter == sync_message_tasks_.end()) + return false; + + if (paused_) + return true; + + MultiplexRouter::Task* task = iter->second.front(); + iter->second.pop_front(); + + DCHECK(task->IsMessageTask()); + MessageWrapper message_wrapper = std::move(task->message_wrapper); + + // Note: after this call, |task| and |iter| may be invalidated. + bool processed = ProcessIncomingMessage( + &message_wrapper.value(), ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES, + nullptr); + DCHECK(processed); + + iter = sync_message_tasks_.find(id); + if (iter == sync_message_tasks_.end()) + return false; + + if (iter->second.empty()) { + sync_message_tasks_.erase(iter); + return false; + } + + return true; +} + +bool MultiplexRouter::ProcessNotifyErrorTask( + Task* task, + ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner) { + DCHECK(!current_task_runner || current_task_runner->BelongsToCurrentThread()); + DCHECK(!paused_); + + AssertLockAcquired(); + InterfaceEndpoint* endpoint = task->endpoint_to_notify.get(); + if (!endpoint->client()) + return true; + + if (client_call_behavior != ALLOW_DIRECT_CLIENT_CALLS || + endpoint->task_runner() != current_task_runner) { + MaybePostToProcessTasks(endpoint->task_runner()); + return false; + } + + DCHECK(endpoint->task_runner()->BelongsToCurrentThread()); + + InterfaceEndpointClient* client = endpoint->client(); + base::Optional disconnect_reason( + endpoint->disconnect_reason()); + + { + // We must unlock before calling into |client| because it may call this + // object within NotifyError(). Holding the lock will lead to deadlock. + // + // It is safe to call into |client| without the lock. Because |client| is + // always accessed on the same thread, including DetachEndpointClient(). + MayAutoUnlock unlocker(&lock_); + client->NotifyError(disconnect_reason); + } + return true; +} + +bool MultiplexRouter::ProcessIncomingMessage( + Message* message, + ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner) { + DCHECK(!current_task_runner || current_task_runner->BelongsToCurrentThread()); + DCHECK(!paused_); + DCHECK(message); + AssertLockAcquired(); + + if (message->IsNull()) { + // This is a sync message and has been processed during sync handle + // watching. + return true; + } + + if (PipeControlMessageHandler::IsPipeControlMessage(message)) { + bool result = false; + + { + MayAutoUnlock unlocker(&lock_); + result = control_message_handler_.Accept(message); + } + + if (!result) + RaiseErrorInNonTestingMode(); + + return true; + } + + InterfaceId id = message->interface_id(); + DCHECK(IsValidInterfaceId(id)); + + InterfaceEndpoint* endpoint = FindEndpoint(id); + if (!endpoint || endpoint->closed()) + return true; + + if (!endpoint->client()) { + // We need to wait until a client is attached in order to dispatch further + // messages. + return false; + } + + bool can_direct_call; + if (message->has_flag(Message::kFlagIsSync)) { + can_direct_call = client_call_behavior != NO_DIRECT_CLIENT_CALLS && + endpoint->task_runner()->BelongsToCurrentThread(); + } else { + can_direct_call = client_call_behavior == ALLOW_DIRECT_CLIENT_CALLS && + endpoint->task_runner() == current_task_runner; + } + + if (!can_direct_call) { + MaybePostToProcessTasks(endpoint->task_runner()); + return false; + } + + DCHECK(endpoint->task_runner()->BelongsToCurrentThread()); + + InterfaceEndpointClient* client = endpoint->client(); + bool result = false; + { + // We must unlock before calling into |client| because it may call this + // object within HandleIncomingMessage(). Holding the lock will lead to + // deadlock. + // + // It is safe to call into |client| without the lock. Because |client| is + // always accessed on the same thread, including DetachEndpointClient(). + MayAutoUnlock unlocker(&lock_); + result = client->HandleIncomingMessage(message); + } + if (!result) + RaiseErrorInNonTestingMode(); + + return true; +} + +void MultiplexRouter::MaybePostToProcessTasks( + base::SingleThreadTaskRunner* task_runner) { + AssertLockAcquired(); + if (posted_to_process_tasks_) + return; + + posted_to_process_tasks_ = true; + posted_to_task_runner_ = task_runner; + task_runner->PostTask( + FROM_HERE, base::Bind(&MultiplexRouter::LockAndCallProcessTasks, this)); +} + +void MultiplexRouter::LockAndCallProcessTasks() { + // There is no need to hold a ref to this class in this case because this is + // always called using base::Bind(), which holds a ref. + MayAutoLock locker(&lock_); + posted_to_process_tasks_ = false; + scoped_refptr runner( + std::move(posted_to_task_runner_)); + ProcessTasks(ALLOW_DIRECT_CLIENT_CALLS, runner.get()); +} + +void MultiplexRouter::UpdateEndpointStateMayRemove( + InterfaceEndpoint* endpoint, + EndpointStateUpdateType type) { + if (type == ENDPOINT_CLOSED) { + endpoint->set_closed(); + } else { + endpoint->set_peer_closed(); + // If the interface endpoint is performing a sync watch, this makes sure + // it is notified and eventually exits the sync watch. + endpoint->SignalSyncMessageEvent(); + } + if (endpoint->closed() && endpoint->peer_closed()) + endpoints_.erase(endpoint->id()); +} + +void MultiplexRouter::RaiseErrorInNonTestingMode() { + AssertLockAcquired(); + if (!testing_mode_) + RaiseError(); +} + +MultiplexRouter::InterfaceEndpoint* MultiplexRouter::FindOrInsertEndpoint( + InterfaceId id, + bool* inserted) { + AssertLockAcquired(); + // Either |inserted| is nullptr or it points to a boolean initialized as + // false. + DCHECK(!inserted || !*inserted); + + InterfaceEndpoint* endpoint = FindEndpoint(id); + if (!endpoint) { + endpoint = new InterfaceEndpoint(this, id); + endpoints_[id] = endpoint; + if (inserted) + *inserted = true; + } + + return endpoint; +} + +MultiplexRouter::InterfaceEndpoint* MultiplexRouter::FindEndpoint( + InterfaceId id) { + AssertLockAcquired(); + auto iter = endpoints_.find(id); + return iter != endpoints_.end() ? iter->second.get() : nullptr; +} + +void MultiplexRouter::AssertLockAcquired() { +#if DCHECK_IS_ON() + if (lock_) + lock_->AssertAcquired(); +#endif +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.h b/mojo/public/cpp/bindings/lib/multiplex_router.h new file mode 100644 index 0000000000000000000000000000000000000000..cac138bcb79b4c635a98176ecd2213dc36a1c453 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/multiplex_router.h @@ -0,0 +1,275 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MULTIPLEX_ROUTER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MULTIPLEX_ROUTER_H_ + +#include + +#include +#include +#include +#include + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/associated_group_controller.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/connector.h" +#include "mojo/public/cpp/bindings/filter_chain.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/message_header_validator.h" +#include "mojo/public/cpp/bindings/pipe_control_message_handler.h" +#include "mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h" +#include "mojo/public/cpp/bindings/pipe_control_message_proxy.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace mojo { + +namespace internal { + +// MultiplexRouter supports routing messages for multiple interfaces over a +// single message pipe. +// +// It is created on the thread where the master interface of the message pipe +// lives. Although it is ref-counted, it is guarateed to be destructed on the +// same thread. +// Some public methods are only allowed to be called on the creating thread; +// while the others are safe to call from any threads. Please see the method +// comments for more details. +// +// NOTE: CloseMessagePipe() or PassMessagePipe() MUST be called on |runner|'s +// thread before this object is destroyed. +class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter + : NON_EXPORTED_BASE(public MessageReceiver), + public AssociatedGroupController, + NON_EXPORTED_BASE(public PipeControlMessageHandlerDelegate) { + public: + enum Config { + // There is only the master interface running on this router. Please note + // that because of interface versioning, the other side of the message pipe + // may use a newer master interface definition which passes associated + // interfaces. In that case, this router may still receive pipe control + // messages or messages targetting associated interfaces. + SINGLE_INTERFACE, + // Similar to the mode above, there is only the master interface running on + // this router. Besides, the master interface has sync methods. + SINGLE_INTERFACE_WITH_SYNC_METHODS, + // There may be associated interfaces running on this router. + MULTI_INTERFACE + }; + + // If |set_interface_id_namespace_bit| is true, the interface IDs generated by + // this router will have the highest bit set. + MultiplexRouter(ScopedMessagePipeHandle message_pipe, + Config config, + bool set_interface_id_namespace_bit, + scoped_refptr runner); + + // Sets the master interface name for this router. Only used when reporting + // message header or control message validation errors. + // |name| must be a string literal. + void SetMasterInterfaceName(const char* name); + + // --------------------------------------------------------------------------- + // The following public methods are safe to call from any threads. + + // AssociatedGroupController implementation: + InterfaceId AssociateInterface( + ScopedInterfaceEndpointHandle handle_to_send) override; + ScopedInterfaceEndpointHandle CreateLocalEndpointHandle( + InterfaceId id) override; + void CloseEndpointHandle( + InterfaceId id, + const base::Optional& reason) override; + InterfaceEndpointController* AttachEndpointClient( + const ScopedInterfaceEndpointHandle& handle, + InterfaceEndpointClient* endpoint_client, + scoped_refptr runner) override; + void DetachEndpointClient( + const ScopedInterfaceEndpointHandle& handle) override; + void RaiseError() override; + + // --------------------------------------------------------------------------- + // The following public methods are called on the creating thread. + + // Please note that this method shouldn't be called unless it results from an + // explicit request of the user of bindings (e.g., the user sets an + // InterfacePtr to null or closes a Binding). + void CloseMessagePipe(); + + // Extracts the underlying message pipe. + ScopedMessagePipeHandle PassMessagePipe() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!HasAssociatedEndpoints()); + return connector_.PassMessagePipe(); + } + + // Blocks the current thread until the first incoming message, or |deadline|. + bool WaitForIncomingMessage(MojoDeadline deadline) { + DCHECK(thread_checker_.CalledOnValidThread()); + return connector_.WaitForIncomingMessage(deadline); + } + + // See Binding for details of pause/resume. + void PauseIncomingMethodCallProcessing(); + void ResumeIncomingMethodCallProcessing(); + + // Whether there are any associated interfaces running currently. + bool HasAssociatedEndpoints() const; + + // Sets this object to testing mode. + // In testing mode, the object doesn't disconnect the underlying message pipe + // when it receives unexpected or invalid messages. + void EnableTestingMode(); + + // Is the router bound to a message pipe handle? + bool is_valid() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return connector_.is_valid(); + } + + // TODO(yzshen): consider removing this getter. + MessagePipeHandle handle() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return connector_.handle(); + } + + bool SimulateReceivingMessageForTesting(Message* message) { + return filters_.Accept(message); + } + + private: + class InterfaceEndpoint; + class MessageWrapper; + struct Task; + + ~MultiplexRouter() override; + + // MessageReceiver implementation: + bool Accept(Message* message) override; + + // PipeControlMessageHandlerDelegate implementation: + bool OnPeerAssociatedEndpointClosed( + InterfaceId id, + const base::Optional& reason) override; + + void OnPipeConnectionError(); + + // Specifies whether we are allowed to directly call into + // InterfaceEndpointClient (given that we are already on the same thread as + // the client). + enum ClientCallBehavior { + // Don't call any InterfaceEndpointClient methods directly. + NO_DIRECT_CLIENT_CALLS, + // Only call InterfaceEndpointClient::HandleIncomingMessage directly to + // handle sync messages. + ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES, + // Allow to call any InterfaceEndpointClient methods directly. + ALLOW_DIRECT_CLIENT_CALLS + }; + + // Processes enqueued tasks (incoming messages and error notifications). + // |current_task_runner| is only used when |client_call_behavior| is + // ALLOW_DIRECT_CLIENT_CALLS to determine whether we are on the right task + // runner to make client calls for async messages or connection error + // notifications. + // + // Note: Because calling into InterfaceEndpointClient may lead to destruction + // of this object, if direct calls are allowed, the caller needs to hold on to + // a ref outside of |lock_| before calling this method. + void ProcessTasks(ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner); + + // Processes the first queued sync message for the endpoint corresponding to + // |id|; returns whether there are more sync messages for that endpoint in the + // queue. + // + // This method is only used by enpoints during sync watching. Therefore, not + // all sync messages are handled by it. + bool ProcessFirstSyncMessageForEndpoint(InterfaceId id); + + // Returns true to indicate that |task|/|message| has been processed. + bool ProcessNotifyErrorTask( + Task* task, + ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner); + bool ProcessIncomingMessage( + Message* message, + ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner); + + void MaybePostToProcessTasks(base::SingleThreadTaskRunner* task_runner); + void LockAndCallProcessTasks(); + + // Updates the state of |endpoint|. If both the endpoint and its peer have + // been closed, removes it from |endpoints_|. + // NOTE: The method may invalidate |endpoint|. + enum EndpointStateUpdateType { ENDPOINT_CLOSED, PEER_ENDPOINT_CLOSED }; + void UpdateEndpointStateMayRemove(InterfaceEndpoint* endpoint, + EndpointStateUpdateType type); + + void RaiseErrorInNonTestingMode(); + + InterfaceEndpoint* FindOrInsertEndpoint(InterfaceId id, bool* inserted); + InterfaceEndpoint* FindEndpoint(InterfaceId id); + + void AssertLockAcquired(); + + // Whether to set the namespace bit when generating interface IDs. Please see + // comments of kInterfaceIdNamespaceMask. + const bool set_interface_id_namespace_bit_; + + scoped_refptr task_runner_; + + // Owned by |filters_| below. + MessageHeaderValidator* header_validator_; + + FilterChain filters_; + Connector connector_; + + base::ThreadChecker thread_checker_; + + // Protects the following members. + // Not set in Config::SINGLE_INTERFACE* mode. + mutable base::Optional lock_; + PipeControlMessageHandler control_message_handler_; + + // NOTE: It is unsafe to call into this object while holding |lock_|. + PipeControlMessageProxy control_message_proxy_; + + std::map> endpoints_; + uint32_t next_interface_id_value_; + + std::deque> tasks_; + // It refers to tasks in |tasks_| and doesn't own any of them. + std::map> sync_message_tasks_; + + bool posted_to_process_tasks_; + scoped_refptr posted_to_task_runner_; + + bool encountered_error_; + + bool paused_; + + bool testing_mode_; + + DISALLOW_COPY_AND_ASSIGN(MultiplexRouter); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MULTIPLEX_ROUTER_H_ diff --git a/mojo/public/cpp/bindings/lib/native_enum_data.h b/mojo/public/cpp/bindings/lib/native_enum_data.h new file mode 100644 index 0000000000000000000000000000000000000000..dcafce2815cfe2648aec4b1bf05eff2d45083f91 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_enum_data.h @@ -0,0 +1,26 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_DATA_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_DATA_H_ + +namespace mojo { +namespace internal { + +class ValidationContext; + +class NativeEnum_Data { + public: + static bool const kIsExtensible = true; + + static bool IsKnownValue(int32_t value) { return false; } + + static bool Validate(int32_t value, + ValidationContext* validation_context) { return true; } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_DATA_H_ diff --git a/mojo/public/cpp/bindings/lib/native_enum_serialization.h b/mojo/public/cpp/bindings/lib/native_enum_serialization.h new file mode 100644 index 0000000000000000000000000000000000000000..4faf957c58e75d98c598d12b316d9b34679a7639 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_enum_serialization.h @@ -0,0 +1,82 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_SERIALIZATION_H_ + +#include +#include + +#include + +#include "base/logging.h" +#include "base/pickle.h" +#include "ipc/ipc_param_traits.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/bindings/native_enum.h" + +namespace mojo { +namespace internal { + +template +struct NativeEnumSerializerImpl { + using UserType = typename std::remove_const::type; + using Traits = IPC::ParamTraits; + + // IPC_ENUM_TRAITS* macros serialize enum as int, make sure that fits into + // mojo native-only enum. + static_assert(sizeof(NativeEnum) >= sizeof(int), + "Cannot store the serialization result in NativeEnum."); + + static void Serialize(UserType input, int32_t* output) { + base::Pickle pickle; + Traits::Write(&pickle, input); + + CHECK_GE(sizeof(int32_t), pickle.payload_size()); + *output = 0; + memcpy(reinterpret_cast(output), pickle.payload(), + pickle.payload_size()); + } + + struct PickleData { + uint32_t payload_size; + int32_t value; + }; + static_assert(sizeof(PickleData) == 8, "PickleData size mismatch."); + + static bool Deserialize(int32_t input, UserType* output) { + PickleData data = {sizeof(int32_t), input}; + base::Pickle pickle_view(reinterpret_cast(&data), + sizeof(PickleData)); + base::PickleIterator iter(pickle_view); + return Traits::Read(&pickle_view, &iter, output); + } +}; + +struct UnmappedNativeEnumSerializerImpl { + static void Serialize(NativeEnum input, int32_t* output) { + *output = static_cast(input); + } + static bool Deserialize(int32_t input, NativeEnum* output) { + *output = static_cast(input); + return true; + } +}; + +template <> +struct NativeEnumSerializerImpl + : public UnmappedNativeEnumSerializerImpl {}; + +template <> +struct NativeEnumSerializerImpl + : public UnmappedNativeEnumSerializerImpl {}; + +template +struct Serializer + : public NativeEnumSerializerImpl {}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/native_struct.cc b/mojo/public/cpp/bindings/lib/native_struct.cc new file mode 100644 index 0000000000000000000000000000000000000000..7b1a1a6c594f6429cd58bdd7aecd537c1bd68f26 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_struct.cc @@ -0,0 +1,34 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/native_struct.h" + +#include "mojo/public/cpp/bindings/lib/hash_util.h" + +namespace mojo { + +// static +NativeStructPtr NativeStruct::New() { + return NativeStructPtr(base::in_place); +} + +NativeStruct::NativeStruct() {} + +NativeStruct::~NativeStruct() {} + +NativeStructPtr NativeStruct::Clone() const { + NativeStructPtr rv(New()); + rv->data = data; + return rv; +} + +bool NativeStruct::Equals(const NativeStruct& other) const { + return data == other.data; +} + +size_t NativeStruct::Hash(size_t seed) const { + return internal::Hash(seed, data); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/native_struct_data.cc b/mojo/public/cpp/bindings/lib/native_struct_data.cc new file mode 100644 index 0000000000000000000000000000000000000000..0e5d2456927f823f08a1c5a9f72ceccebd12a9a7 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_struct_data.cc @@ -0,0 +1,22 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/lib/native_struct_data.h" + +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" + +namespace mojo { +namespace internal { + +// static +bool NativeStruct_Data::Validate(const void* data, + ValidationContext* validation_context) { + const ContainerValidateParams data_validate_params(0, false, nullptr); + return Array_Data::Validate(data, validation_context, + &data_validate_params); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/native_struct_data.h b/mojo/public/cpp/bindings/lib/native_struct_data.h new file mode 100644 index 0000000000000000000000000000000000000000..1c7cd81c7703bd90bb59b02af11d1a7d16a23eda --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_struct_data.h @@ -0,0 +1,38 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_DATA_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_DATA_H_ + +#include + +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { +namespace internal { + +class ValidationContext; + +class MOJO_CPP_BINDINGS_EXPORT NativeStruct_Data { + public: + static bool Validate(const void* data, ValidationContext* validation_context); + + // Unlike normal structs, the memory layout is exactly the same as an array + // of uint8_t. + Array_Data data; + + private: + NativeStruct_Data() = delete; + ~NativeStruct_Data() = delete; +}; + +static_assert(sizeof(Array_Data) == sizeof(NativeStruct_Data), + "Mismatched NativeStruct_Data and Array_Data size"); + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_DATA_H_ diff --git a/mojo/public/cpp/bindings/lib/native_struct_serialization.cc b/mojo/public/cpp/bindings/lib/native_struct_serialization.cc new file mode 100644 index 0000000000000000000000000000000000000000..fa0dbf380342950b6888df6ec29fd62f82539f7f --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_struct_serialization.cc @@ -0,0 +1,61 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/lib/native_struct_serialization.h" + +#include "mojo/public/cpp/bindings/lib/serialization.h" + +namespace mojo { +namespace internal { + +// static +size_t UnmappedNativeStructSerializerImpl::PrepareToSerialize( + const NativeStructPtr& input, + SerializationContext* context) { + if (!input) + return 0; + return internal::PrepareToSerialize>(input->data, + context); +} + +// static +void UnmappedNativeStructSerializerImpl::Serialize( + const NativeStructPtr& input, + Buffer* buffer, + NativeStruct_Data** output, + SerializationContext* context) { + if (!input) { + *output = nullptr; + return; + } + + Array_Data* data = nullptr; + const ContainerValidateParams params(0, false, nullptr); + internal::Serialize>(input->data, buffer, &data, + ¶ms, context); + *output = reinterpret_cast(data); +} + +// static +bool UnmappedNativeStructSerializerImpl::Deserialize( + NativeStruct_Data* input, + NativeStructPtr* output, + SerializationContext* context) { + Array_Data* data = reinterpret_cast*>(input); + + NativeStructPtr result(NativeStruct::New()); + if (!internal::Deserialize>(data, &result->data, + context)) { + output = nullptr; + return false; + } + if (!result->data) + *output = nullptr; + else + result.Swap(output); + return true; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/native_struct_serialization.h b/mojo/public/cpp/bindings/lib/native_struct_serialization.h new file mode 100644 index 0000000000000000000000000000000000000000..457435b9558d8dda5a76d57e38566f7d879684b9 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_struct_serialization.h @@ -0,0 +1,134 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_SERIALIZATION_H_ + +#include +#include + +#include + +#include "base/logging.h" +#include "base/pickle.h" +#include "ipc/ipc_param_traits.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/native_struct_data.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/native_struct.h" +#include "mojo/public/cpp/bindings/native_struct_data_view.h" + +namespace mojo { +namespace internal { + +template +struct NativeStructSerializerImpl { + using UserType = typename std::remove_const::type; + using Traits = IPC::ParamTraits; + + static size_t PrepareToSerialize(MaybeConstUserType& value, + SerializationContext* context) { + base::PickleSizer sizer; + Traits::GetSize(&sizer, value); + return Align(sizer.payload_size() + sizeof(ArrayHeader)); + } + + static void Serialize(MaybeConstUserType& value, + Buffer* buffer, + NativeStruct_Data** out, + SerializationContext* context) { + base::Pickle pickle; + Traits::Write(&pickle, value); + +#if DCHECK_IS_ON() + base::PickleSizer sizer; + Traits::GetSize(&sizer, value); + DCHECK_EQ(sizer.payload_size(), pickle.payload_size()); +#endif + + size_t total_size = pickle.payload_size() + sizeof(ArrayHeader); + DCHECK_LT(total_size, std::numeric_limits::max()); + + // Allocate a uint8 array, initialize its header, and copy the Pickle in. + ArrayHeader* header = + reinterpret_cast(buffer->Allocate(total_size)); + header->num_bytes = static_cast(total_size); + header->num_elements = static_cast(pickle.payload_size()); + memcpy(reinterpret_cast(header) + sizeof(ArrayHeader), + pickle.payload(), pickle.payload_size()); + + *out = reinterpret_cast(header); + } + + static bool Deserialize(NativeStruct_Data* data, + UserType* out, + SerializationContext* context) { + if (!data) + return false; + + // Construct a temporary base::Pickle view over the array data. Note that + // the Array_Data is laid out like this: + // + // [num_bytes (4 bytes)] [num_elements (4 bytes)] [elements...] + // + // and base::Pickle expects to view data like this: + // + // [payload_size (4 bytes)] [header bytes ...] [payload...] + // + // Because ArrayHeader's num_bytes includes the length of the header and + // Pickle's payload_size does not, we need to adjust the stored value + // momentarily so Pickle can view the data. + ArrayHeader* header = reinterpret_cast(data); + DCHECK_GE(header->num_bytes, sizeof(ArrayHeader)); + header->num_bytes -= sizeof(ArrayHeader); + + { + // Construct a view over the full Array_Data, including our hacked up + // header. Pickle will infer from this that the header is 8 bytes long, + // and the payload will contain all of the pickled bytes. + base::Pickle pickle_view(reinterpret_cast(header), + header->num_bytes + sizeof(ArrayHeader)); + base::PickleIterator iter(pickle_view); + if (!Traits::Read(&pickle_view, &iter, out)) + return false; + } + + // Return the header to its original state. + header->num_bytes += sizeof(ArrayHeader); + + return true; + } +}; + +struct MOJO_CPP_BINDINGS_EXPORT UnmappedNativeStructSerializerImpl { + static size_t PrepareToSerialize(const NativeStructPtr& input, + SerializationContext* context); + static void Serialize(const NativeStructPtr& input, + Buffer* buffer, + NativeStruct_Data** output, + SerializationContext* context); + static bool Deserialize(NativeStruct_Data* input, + NativeStructPtr* output, + SerializationContext* context); +}; + +template <> +struct NativeStructSerializerImpl + : public UnmappedNativeStructSerializerImpl {}; + +template <> +struct NativeStructSerializerImpl + : public UnmappedNativeStructSerializerImpl {}; + +template +struct Serializer + : public NativeStructSerializerImpl {}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc b/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc new file mode 100644 index 0000000000000000000000000000000000000000..d451c05a5fb888a0e9ca16f9df72fcd0ef86c441 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc @@ -0,0 +1,90 @@ +// 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. + +#include "mojo/public/cpp/bindings/pipe_control_message_handler.h" + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" +#include "mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h" +#include "mojo/public/interfaces/bindings/pipe_control_messages.mojom.h" + +namespace mojo { + +PipeControlMessageHandler::PipeControlMessageHandler( + PipeControlMessageHandlerDelegate* delegate) + : delegate_(delegate) {} + +PipeControlMessageHandler::~PipeControlMessageHandler() {} + +void PipeControlMessageHandler::SetDescription(const std::string& description) { + description_ = description; +} + +// static +bool PipeControlMessageHandler::IsPipeControlMessage(const Message* message) { + return !IsValidInterfaceId(message->interface_id()); +} + +bool PipeControlMessageHandler::Accept(Message* message) { + if (!Validate(message)) + return false; + + if (message->name() == pipe_control::kRunOrClosePipeMessageId) + return RunOrClosePipe(message); + + NOTREACHED(); + return false; +} + +bool PipeControlMessageHandler::Validate(Message* message) { + internal::ValidationContext validation_context(message->payload(), + message->payload_num_bytes(), + 0, 0, message, description_); + + if (message->name() == pipe_control::kRunOrClosePipeMessageId) { + if (!internal::ValidateMessageIsRequestWithoutResponse( + message, &validation_context)) { + return false; + } + return internal::ValidateMessagePayload< + pipe_control::internal::RunOrClosePipeMessageParams_Data>( + message, &validation_context); + } + + return false; +} + +bool PipeControlMessageHandler::RunOrClosePipe(Message* message) { + internal::SerializationContext context; + pipe_control::internal::RunOrClosePipeMessageParams_Data* params = + reinterpret_cast< + pipe_control::internal::RunOrClosePipeMessageParams_Data*>( + message->mutable_payload()); + pipe_control::RunOrClosePipeMessageParamsPtr params_ptr; + internal::Deserialize( + params, ¶ms_ptr, &context); + + if (params_ptr->input->is_peer_associated_endpoint_closed_event()) { + const auto& event = + params_ptr->input->get_peer_associated_endpoint_closed_event(); + + base::Optional reason; + if (event->disconnect_reason) { + reason.emplace(event->disconnect_reason->custom_reason, + event->disconnect_reason->description); + } + return delegate_->OnPeerAssociatedEndpointClosed(event->id, reason); + } + + DVLOG(1) << "Unsupported command in a RunOrClosePipe message pipe control " + << "message. Closing the pipe."; + return false; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc new file mode 100644 index 0000000000000000000000000000000000000000..1029c2c4918c9f3fbe821a496662ac02810c7182 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc @@ -0,0 +1,68 @@ +// 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. + +#include "mojo/public/cpp/bindings/pipe_control_message_proxy.h" + +#include +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/interfaces/bindings/pipe_control_messages.mojom.h" + +namespace mojo { +namespace { + +Message ConstructRunOrClosePipeMessage( + pipe_control::RunOrClosePipeInputPtr input_ptr) { + internal::SerializationContext context; + + auto params_ptr = pipe_control::RunOrClosePipeMessageParams::New(); + params_ptr->input = std::move(input_ptr); + + size_t size = internal::PrepareToSerialize< + pipe_control::RunOrClosePipeMessageParamsDataView>(params_ptr, &context); + internal::MessageBuilder builder(pipe_control::kRunOrClosePipeMessageId, 0, + size, 0); + + pipe_control::internal::RunOrClosePipeMessageParams_Data* params = nullptr; + internal::Serialize( + params_ptr, builder.buffer(), ¶ms, &context); + builder.message()->set_interface_id(kInvalidInterfaceId); + return std::move(*builder.message()); +} + +} // namespace + +PipeControlMessageProxy::PipeControlMessageProxy(MessageReceiver* receiver) + : receiver_(receiver) {} + +void PipeControlMessageProxy::NotifyPeerEndpointClosed( + InterfaceId id, + const base::Optional& reason) { + Message message(ConstructPeerEndpointClosedMessage(id, reason)); + ignore_result(receiver_->Accept(&message)); +} + +// static +Message PipeControlMessageProxy::ConstructPeerEndpointClosedMessage( + InterfaceId id, + const base::Optional& reason) { + auto event = pipe_control::PeerAssociatedEndpointClosedEvent::New(); + event->id = id; + if (reason) { + event->disconnect_reason = pipe_control::DisconnectReason::New(); + event->disconnect_reason->custom_reason = reason->custom_reason; + event->disconnect_reason->description = reason->description; + } + + auto input = pipe_control::RunOrClosePipeInput::New(); + input->set_peer_associated_endpoint_closed_event(std::move(event)); + + return ConstructRunOrClosePipeMessage(std::move(input)); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc b/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc new file mode 100644 index 0000000000000000000000000000000000000000..c1345079a5c901d3bf339a93c47c8689de07769b --- /dev/null +++ b/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc @@ -0,0 +1,382 @@ +// 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. + +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "mojo/public/cpp/bindings/associated_group_controller.h" +#include "mojo/public/cpp/bindings/lib/may_auto_lock.h" + +namespace mojo { + +// ScopedInterfaceEndpointHandle::State ---------------------------------------- + +// State could be called from multiple threads. +class ScopedInterfaceEndpointHandle::State + : public base::RefCountedThreadSafe { + public: + State() = default; + + State(InterfaceId id, + scoped_refptr group_controller) + : id_(id), group_controller_(group_controller) {} + + void InitPendingState(scoped_refptr peer) { + DCHECK(!lock_); + DCHECK(!pending_association_); + + lock_.emplace(); + pending_association_ = true; + peer_state_ = std::move(peer); + } + + void Close(const base::Optional& reason) { + scoped_refptr cached_group_controller; + InterfaceId cached_id = kInvalidInterfaceId; + scoped_refptr cached_peer_state; + + { + internal::MayAutoLock locker(&lock_); + + if (!association_event_handler_.is_null()) { + association_event_handler_.Reset(); + runner_ = nullptr; + } + + if (!pending_association_) { + if (IsValidInterfaceId(id_)) { + // Intentionally keep |group_controller_| unchanged. + // That is because the callback created by + // CreateGroupControllerGetter() could still be used after this point, + // potentially from another thread. We would like it to continue + // returning the same group controller. + // + // Imagine there is a ThreadSafeForwarder A: + // (1) On the IO thread, A's underlying associated interface pointer + // is closed. + // (2) On the proxy thread, the user makes a call on A to pass an + // associated request B_asso_req. The callback returned by + // CreateGroupControllerGetter() is used to associate B_asso_req. + // (3) On the proxy thread, the user immediately binds B_asso_ptr_info + // to B_asso_ptr and makes calls on it. + // + // If we reset |group_controller_| in step (1), step (2) won't be able + // to associate B_asso_req. Therefore, in step (3) B_asso_ptr won't be + // able to serialize associated endpoints or send message because it + // is still in "pending_association" state and doesn't have a group + // controller. + // + // We could "address" this issue by ignoring messages if there isn't a + // group controller. But the side effect is that we cannot detect + // programming errors of "using associated interface pointer before + // sending associated request". + + cached_group_controller = group_controller_; + cached_id = id_; + id_ = kInvalidInterfaceId; + } + } else { + pending_association_ = false; + cached_peer_state = std::move(peer_state_); + } + } + + if (cached_group_controller) { + cached_group_controller->CloseEndpointHandle(cached_id, reason); + } else if (cached_peer_state) { + cached_peer_state->OnPeerClosedBeforeAssociation(reason); + } + } + + void SetAssociationEventHandler(AssociationEventCallback handler) { + internal::MayAutoLock locker(&lock_); + + if (!pending_association_ && !IsValidInterfaceId(id_)) + return; + + association_event_handler_ = std::move(handler); + if (association_event_handler_.is_null()) { + runner_ = nullptr; + return; + } + + runner_ = base::ThreadTaskRunnerHandle::Get(); + if (!pending_association_) { + runner_->PostTask( + FROM_HERE, + base::Bind( + &ScopedInterfaceEndpointHandle::State::RunAssociationEventHandler, + this, runner_, ASSOCIATED)); + } else if (!peer_state_) { + runner_->PostTask( + FROM_HERE, + base::Bind( + &ScopedInterfaceEndpointHandle::State::RunAssociationEventHandler, + this, runner_, PEER_CLOSED_BEFORE_ASSOCIATION)); + } + } + + bool NotifyAssociation( + InterfaceId id, + scoped_refptr peer_group_controller) { + scoped_refptr cached_peer_state; + { + internal::MayAutoLock locker(&lock_); + + DCHECK(pending_association_); + pending_association_ = false; + cached_peer_state = std::move(peer_state_); + } + + if (cached_peer_state) { + cached_peer_state->OnAssociated(id, std::move(peer_group_controller)); + return true; + } + return false; + } + + bool is_valid() const { + internal::MayAutoLock locker(&lock_); + return pending_association_ || IsValidInterfaceId(id_); + } + + bool pending_association() const { + internal::MayAutoLock locker(&lock_); + return pending_association_; + } + + InterfaceId id() const { + internal::MayAutoLock locker(&lock_); + return id_; + } + + AssociatedGroupController* group_controller() const { + internal::MayAutoLock locker(&lock_); + return group_controller_.get(); + } + + const base::Optional& disconnect_reason() const { + internal::MayAutoLock locker(&lock_); + return disconnect_reason_; + } + + private: + friend class base::RefCountedThreadSafe; + + ~State() { + DCHECK(!pending_association_); + DCHECK(!IsValidInterfaceId(id_)); + } + + // Called by the peer, maybe from a different thread. + void OnAssociated(InterfaceId id, + scoped_refptr group_controller) { + AssociationEventCallback handler; + { + internal::MayAutoLock locker(&lock_); + + // There may be race between Close() of endpoint A and + // NotifyPeerAssociation() of endpoint A_peer on different threads. + // Therefore, it is possible that endpoint A has been closed but it + // still gets OnAssociated() call from its peer. + if (!pending_association_) + return; + + pending_association_ = false; + peer_state_ = nullptr; + id_ = id; + group_controller_ = std::move(group_controller); + + if (!association_event_handler_.is_null()) { + if (runner_->BelongsToCurrentThread()) { + handler = std::move(association_event_handler_); + runner_ = nullptr; + } else { + runner_->PostTask(FROM_HERE, + base::Bind(&ScopedInterfaceEndpointHandle::State:: + RunAssociationEventHandler, + this, runner_, ASSOCIATED)); + } + } + } + + if (!handler.is_null()) + std::move(handler).Run(ASSOCIATED); + } + + // Called by the peer, maybe from a different thread. + void OnPeerClosedBeforeAssociation( + const base::Optional& reason) { + AssociationEventCallback handler; + { + internal::MayAutoLock locker(&lock_); + + // There may be race between Close()/NotifyPeerAssociation() of endpoint + // A and Close() of endpoint A_peer on different threads. + // Therefore, it is possible that endpoint A is not in pending association + // state but still gets OnPeerClosedBeforeAssociation() call from its + // peer. + if (!pending_association_) + return; + + disconnect_reason_ = reason; + // NOTE: This handle itself is still pending. + peer_state_ = nullptr; + + if (!association_event_handler_.is_null()) { + if (runner_->BelongsToCurrentThread()) { + handler = std::move(association_event_handler_); + runner_ = nullptr; + } else { + runner_->PostTask( + FROM_HERE, + base::Bind(&ScopedInterfaceEndpointHandle::State:: + RunAssociationEventHandler, + this, runner_, PEER_CLOSED_BEFORE_ASSOCIATION)); + } + } + } + + if (!handler.is_null()) + std::move(handler).Run(PEER_CLOSED_BEFORE_ASSOCIATION); + } + + void RunAssociationEventHandler( + scoped_refptr posted_to_runner, + AssociationEvent event) { + AssociationEventCallback handler; + + { + internal::MayAutoLock locker(&lock_); + if (posted_to_runner == runner_) { + runner_ = nullptr; + handler = std::move(association_event_handler_); + } + } + + if (!handler.is_null()) + std::move(handler).Run(event); + } + + // Protects the following members if the handle is initially set to pending + // association. + mutable base::Optional lock_; + + bool pending_association_ = false; + base::Optional disconnect_reason_; + + scoped_refptr peer_state_; + + AssociationEventCallback association_event_handler_; + scoped_refptr runner_; + + InterfaceId id_ = kInvalidInterfaceId; + scoped_refptr group_controller_; + + DISALLOW_COPY_AND_ASSIGN(State); +}; + +// ScopedInterfaceEndpointHandle ----------------------------------------------- + +// static +void ScopedInterfaceEndpointHandle::CreatePairPendingAssociation( + ScopedInterfaceEndpointHandle* handle0, + ScopedInterfaceEndpointHandle* handle1) { + ScopedInterfaceEndpointHandle result0; + ScopedInterfaceEndpointHandle result1; + result0.state_->InitPendingState(result1.state_); + result1.state_->InitPendingState(result0.state_); + + *handle0 = std::move(result0); + *handle1 = std::move(result1); +} + +ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle() + : state_(new State) {} + +ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle( + ScopedInterfaceEndpointHandle&& other) + : state_(new State) { + state_.swap(other.state_); +} + +ScopedInterfaceEndpointHandle::~ScopedInterfaceEndpointHandle() { + state_->Close(base::nullopt); +} + +ScopedInterfaceEndpointHandle& ScopedInterfaceEndpointHandle::operator=( + ScopedInterfaceEndpointHandle&& other) { + reset(); + state_.swap(other.state_); + return *this; +} + +bool ScopedInterfaceEndpointHandle::is_valid() const { + return state_->is_valid(); +} + +bool ScopedInterfaceEndpointHandle::pending_association() const { + return state_->pending_association(); +} + +InterfaceId ScopedInterfaceEndpointHandle::id() const { + return state_->id(); +} + +AssociatedGroupController* ScopedInterfaceEndpointHandle::group_controller() + const { + return state_->group_controller(); +} + +const base::Optional& +ScopedInterfaceEndpointHandle::disconnect_reason() const { + return state_->disconnect_reason(); +} + +void ScopedInterfaceEndpointHandle::SetAssociationEventHandler( + AssociationEventCallback handler) { + state_->SetAssociationEventHandler(std::move(handler)); +} + +void ScopedInterfaceEndpointHandle::reset() { + ResetInternal(base::nullopt); +} + +void ScopedInterfaceEndpointHandle::ResetWithReason( + uint32_t custom_reason, + const std::string& description) { + ResetInternal(DisconnectReason(custom_reason, description)); +} + +ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle( + InterfaceId id, + scoped_refptr group_controller) + : state_(new State(id, std::move(group_controller))) { + DCHECK(!IsValidInterfaceId(state_->id()) || state_->group_controller()); +} + +bool ScopedInterfaceEndpointHandle::NotifyAssociation( + InterfaceId id, + scoped_refptr peer_group_controller) { + return state_->NotifyAssociation(id, peer_group_controller); +} + +void ScopedInterfaceEndpointHandle::ResetInternal( + const base::Optional& reason) { + scoped_refptr new_state(new State); + state_->Close(reason); + state_.swap(new_state); +} + +base::Callback +ScopedInterfaceEndpointHandle::CreateGroupControllerGetter() const { + // We allow this callback to be run on any thread. If this handle is created + // in non-pending state, we don't have a lock but it should still be safe + // because the group controller never changes. + return base::Bind(&State::group_controller, state_); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/serialization.h b/mojo/public/cpp/bindings/lib/serialization.h new file mode 100644 index 0000000000000000000000000000000000000000..2a7d288d55492322ab451052e727d3fbb18c349c --- /dev/null +++ b/mojo/public/cpp/bindings/lib/serialization.h @@ -0,0 +1,107 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_H_ + +#include + +#include "mojo/public/cpp/bindings/array_traits_carray.h" +#include "mojo/public/cpp/bindings/array_traits_stl.h" +#include "mojo/public/cpp/bindings/lib/array_serialization.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/bindings/lib/handle_interface_serialization.h" +#include "mojo/public/cpp/bindings/lib/map_serialization.h" +#include "mojo/public/cpp/bindings/lib/native_enum_serialization.h" +#include "mojo/public/cpp/bindings/lib/native_struct_serialization.h" +#include "mojo/public/cpp/bindings/lib/string_serialization.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/map_traits_stl.h" +#include "mojo/public/cpp/bindings/string_traits_stl.h" +#include "mojo/public/cpp/bindings/string_traits_string16.h" +#include "mojo/public/cpp/bindings/string_traits_string_piece.h" + +namespace mojo { +namespace internal { + +template +DataArrayType StructSerializeImpl(UserType* input) { + static_assert(BelongsTo::value, + "Unexpected type."); + + SerializationContext context; + size_t size = PrepareToSerialize(*input, &context); + DCHECK_EQ(size, Align(size)); + + DataArrayType result(size); + if (size == 0) + return result; + + void* result_buffer = &result.front(); + // The serialization logic requires that the buffer is 8-byte aligned. If the + // result buffer is not properly aligned, we have to do an extra copy. In + // practice, this should never happen for std::vector. + bool need_copy = !IsAligned(result_buffer); + + if (need_copy) { + // calloc sets the memory to all zero. + result_buffer = calloc(size, 1); + DCHECK(IsAligned(result_buffer)); + } + + Buffer buffer; + buffer.Initialize(result_buffer, size); + typename MojomTypeTraits::Data* data = nullptr; + Serialize(*input, &buffer, &data, &context); + + if (need_copy) { + memcpy(&result.front(), result_buffer, size); + free(result_buffer); + } + + return result; +} + +template +bool StructDeserializeImpl(const DataArrayType& input, + UserType* output, + bool (*validate_func)(const void*, + ValidationContext*)) { + static_assert(BelongsTo::value, + "Unexpected type."); + using DataType = typename MojomTypeTraits::Data; + + // TODO(sammc): Use DataArrayType::empty() once WTF::Vector::empty() exists. + void* input_buffer = + input.size() == 0 + ? nullptr + : const_cast(reinterpret_cast(&input.front())); + + // Please see comments in StructSerializeImpl. + bool need_copy = !IsAligned(input_buffer); + + if (need_copy) { + input_buffer = malloc(input.size()); + DCHECK(IsAligned(input_buffer)); + memcpy(input_buffer, &input.front(), input.size()); + } + + ValidationContext validation_context(input_buffer, input.size(), 0, 0); + bool result = false; + if (validate_func(input_buffer, &validation_context)) { + auto data = reinterpret_cast(input_buffer); + SerializationContext context; + result = Deserialize(data, output, &context); + } + + if (need_copy) + free(input_buffer); + + return result; +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/serialization_context.cc b/mojo/public/cpp/bindings/lib/serialization_context.cc new file mode 100644 index 0000000000000000000000000000000000000000..e2fd5c6e18a1863f994fce38391d977a0eb0b7bb --- /dev/null +++ b/mojo/public/cpp/bindings/lib/serialization_context.cc @@ -0,0 +1,57 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/lib/serialization_context.h" + +#include + +#include "base/logging.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace internal { + +SerializedHandleVector::SerializedHandleVector() {} + +SerializedHandleVector::~SerializedHandleVector() { + for (auto handle : handles_) { + if (handle.is_valid()) { + MojoResult rv = MojoClose(handle.value()); + DCHECK_EQ(rv, MOJO_RESULT_OK); + } + } +} + +Handle_Data SerializedHandleVector::AddHandle(mojo::Handle handle) { + Handle_Data data; + if (!handle.is_valid()) { + data.value = kEncodedInvalidHandleValue; + } else { + DCHECK_LT(handles_.size(), std::numeric_limits::max()); + data.value = static_cast(handles_.size()); + handles_.push_back(handle); + } + return data; +} + +mojo::Handle SerializedHandleVector::TakeHandle( + const Handle_Data& encoded_handle) { + if (!encoded_handle.is_valid()) + return mojo::Handle(); + DCHECK_LT(encoded_handle.value, handles_.size()); + return FetchAndReset(&handles_[encoded_handle.value]); +} + +void SerializedHandleVector::Swap(std::vector* other) { + handles_.swap(*other); +} + +SerializationContext::SerializationContext() {} + +SerializationContext::~SerializationContext() { + DCHECK(!custom_contexts || custom_contexts->empty()); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/serialization_context.h b/mojo/public/cpp/bindings/lib/serialization_context.h new file mode 100644 index 0000000000000000000000000000000000000000..a34fe3d4eda9f87a006e3b19a4735bd14b2c3a84 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/serialization_context.h @@ -0,0 +1,77 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_CONTEXT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_CONTEXT_H_ + +#include + +#include +#include +#include + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { +namespace internal { + +// A container for handles during serialization/deserialization. +class MOJO_CPP_BINDINGS_EXPORT SerializedHandleVector { + public: + SerializedHandleVector(); + ~SerializedHandleVector(); + + size_t size() const { return handles_.size(); } + + // Adds a handle to the handle list and returns its index for encoding. + Handle_Data AddHandle(mojo::Handle handle); + + // Takes a handle from the list of serialized handle data. + mojo::Handle TakeHandle(const Handle_Data& encoded_handle); + + // Takes a handle from the list of serialized handle data and returns it in + // |*out_handle| as a specific scoped handle type. + template + ScopedHandleBase TakeHandleAs(const Handle_Data& encoded_handle) { + return MakeScopedHandle(T(TakeHandle(encoded_handle).value())); + } + + // Swaps all owned handles out with another Handle vector. + void Swap(std::vector* other); + + private: + // Handles are owned by this object. + std::vector handles_; + + DISALLOW_COPY_AND_ASSIGN(SerializedHandleVector); +}; + +// Context information for serialization/deserialization routines. +struct MOJO_CPP_BINDINGS_EXPORT SerializationContext { + SerializationContext(); + + ~SerializationContext(); + + // Opaque context pointers returned by StringTraits::SetUpContext(). + std::unique_ptr> custom_contexts; + + // Stashes handles encoded in a message by index. + SerializedHandleVector handles; + + // The number of ScopedInterfaceEndpointHandles that need to be serialized. + // It is calculated by PrepareToSerialize(). + uint32_t associated_endpoint_count = 0; + + // Stashes ScopedInterfaceEndpointHandles encoded in a message by index. + std::vector associated_endpoint_handles; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_CONTEXT_H_ diff --git a/mojo/public/cpp/bindings/lib/serialization_forward.h b/mojo/public/cpp/bindings/lib/serialization_forward.h new file mode 100644 index 0000000000000000000000000000000000000000..55c9982cccc803a7119a0f95e17c59a70b305595 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/serialization_forward.h @@ -0,0 +1,123 @@ +// Copyright 2016 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_FORWARD_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_FORWARD_H_ + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/array_traits.h" +#include "mojo/public/cpp/bindings/enum_traits.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/map_traits.h" +#include "mojo/public/cpp/bindings/string_traits.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/union_traits.h" + +// This file is included by serialization implementation files to avoid circular +// includes. +// Users of the serialization funtions should include serialization.h (and also +// wtf_serialization.h if necessary). + +namespace mojo { +namespace internal { + +template +struct Serializer; + +template +struct IsOptionalWrapper { + static const bool value = IsSpecializationOf< + base::Optional, + typename std::remove_const< + typename std::remove_reference::type>::type>::value; +}; + +// PrepareToSerialize() must be matched by a Serialize() for the same input +// later. Moreover, within the same SerializationContext if PrepareToSerialize() +// is called for |input_1|, ..., |input_n|, Serialize() must be called for +// those objects in the exact same order. +template ::value>::type* = nullptr> +size_t PrepareToSerialize(InputUserType&& input, Args&&... args) { + return Serializer::type>:: + PrepareToSerialize(std::forward(input), + std::forward(args)...); +} + +template ::value>::type* = nullptr> +void Serialize(InputUserType&& input, Args&&... args) { + Serializer::type>:: + Serialize(std::forward(input), + std::forward(args)...); +} + +template ::value>::type* = nullptr> +bool Deserialize(DataType&& input, InputUserType* output, Args&&... args) { + return Serializer::Deserialize( + std::forward(input), output, std::forward(args)...); +} + +// Specialization that unwraps base::Optional<>. +template ::value>::type* = nullptr> +size_t PrepareToSerialize(InputUserType&& input, Args&&... args) { + if (!input) + return 0; + return PrepareToSerialize(*input, std::forward(args)...); +} + +template ::value>::type* = nullptr> +void Serialize(InputUserType&& input, + Buffer* buffer, + DataType** output, + Args&&... args) { + if (!input) { + *output = nullptr; + return; + } + Serialize(*input, buffer, output, std::forward(args)...); +} + +template ::value>::type* = nullptr> +bool Deserialize(DataType&& input, InputUserType* output, Args&&... args) { + if (!input) { + *output = base::nullopt; + return true; + } + if (!*output) + output->emplace(); + return Deserialize(std::forward(input), &output->value(), + std::forward(args)...); +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_FORWARD_H_ diff --git a/mojo/public/cpp/bindings/lib/serialization_util.h b/mojo/public/cpp/bindings/lib/serialization_util.h new file mode 100644 index 0000000000000000000000000000000000000000..4820a014ec182c21c14ead49534ff57374a6edcc --- /dev/null +++ b/mojo/public/cpp/bindings/lib/serialization_util.h @@ -0,0 +1,213 @@ +// Copyright 2013 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_UTIL_H_ + +#include +#include + +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" + +namespace mojo { +namespace internal { + +template +struct HasIsNullMethod { + template + static char Test(decltype(U::IsNull)*); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template < + typename Traits, + typename UserType, + typename std::enable_if::value>::type* = nullptr> +bool CallIsNullIfExists(const UserType& input) { + return Traits::IsNull(input); +} + +template < + typename Traits, + typename UserType, + typename std::enable_if::value>::type* = nullptr> +bool CallIsNullIfExists(const UserType& input) { + return false; +} +template +struct HasSetToNullMethod { + template + static char Test(decltype(U::SetToNull)*); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template < + typename Traits, + typename UserType, + typename std::enable_if::value>::type* = nullptr> +bool CallSetToNullIfExists(UserType* output) { + Traits::SetToNull(output); + return true; +} + +template ::value>::type* = + nullptr> +bool CallSetToNullIfExists(UserType* output) { + LOG(ERROR) << "A null value is received. But the Struct/Array/StringTraits " + << "class doesn't define a SetToNull() function and therefore is " + << "unable to deserialize the value."; + return false; +} + +template +struct HasSetUpContextMethod { + template + static char Test(decltype(U::SetUpContext)*); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template ::value> +struct CustomContextHelper; + +template +struct CustomContextHelper { + template + static void* SetUp(MaybeConstUserType& input, SerializationContext* context) { + void* custom_context = Traits::SetUpContext(input); + if (!context->custom_contexts) + context->custom_contexts.reset(new std::queue()); + context->custom_contexts->push(custom_context); + return custom_context; + } + + static void* GetNext(SerializationContext* context) { + void* custom_context = context->custom_contexts->front(); + context->custom_contexts->pop(); + return custom_context; + } + + template + static void TearDown(MaybeConstUserType& input, void* custom_context) { + Traits::TearDownContext(input, custom_context); + } +}; + +template +struct CustomContextHelper { + template + static void* SetUp(MaybeConstUserType& input, SerializationContext* context) { + return nullptr; + } + + static void* GetNext(SerializationContext* context) { return nullptr; } + + template + static void TearDown(MaybeConstUserType& input, void* custom_context) { + DCHECK(!custom_context); + } +}; + +template +ReturnType CallWithContext(ReturnType (*f)(ParamType, void*), + InputUserType&& input, + void* context) { + return f(std::forward(input), context); +} + +template +ReturnType CallWithContext(ReturnType (*f)(ParamType), + InputUserType&& input, + void* context) { + return f(std::forward(input)); +} + +template +struct HasGetBeginMethod { + template + static char Test(decltype(U::GetBegin(std::declval()))*); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + HasGetBeginMethod::value>::type* = nullptr> +decltype(Traits::GetBegin(std::declval())) +CallGetBeginIfExists(MaybeConstUserType& input) { + return Traits::GetBegin(input); +} + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + !HasGetBeginMethod::value>::type* = nullptr> +size_t CallGetBeginIfExists(MaybeConstUserType& input) { + return 0; +} + +template +struct HasGetDataMethod { + template + static char Test(decltype(U::GetData(std::declval()))*); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + HasGetDataMethod::value>::type* = nullptr> +decltype(Traits::GetData(std::declval())) +CallGetDataIfExists(MaybeConstUserType& input) { + return Traits::GetData(input); +} + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + !HasGetDataMethod::value>::type* = nullptr> +void* CallGetDataIfExists(MaybeConstUserType& input) { + return nullptr; +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_UTIL_H_ diff --git a/mojo/public/cpp/bindings/lib/string_serialization.h b/mojo/public/cpp/bindings/lib/string_serialization.h new file mode 100644 index 0000000000000000000000000000000000000000..6e0c75857690af53b1a11dd7cdbe70c0b6fea6c5 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/string_serialization.h @@ -0,0 +1,70 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ + +#include +#include + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/string_data_view.h" +#include "mojo/public/cpp/bindings/string_traits.h" + +namespace mojo { +namespace internal { + +template +struct Serializer { + using UserType = typename std::remove_const::type; + using Traits = StringTraits; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + SerializationContext* context) { + if (CallIsNullIfExists(input)) + return 0; + + void* custom_context = CustomContextHelper::SetUp(input, context); + return Align(sizeof(String_Data) + + CallWithContext(Traits::GetSize, input, custom_context)); + } + + static void Serialize(MaybeConstUserType& input, + Buffer* buffer, + String_Data** output, + SerializationContext* context) { + if (CallIsNullIfExists(input)) { + *output = nullptr; + return; + } + + void* custom_context = CustomContextHelper::GetNext(context); + + String_Data* result = String_Data::New( + CallWithContext(Traits::GetSize, input, custom_context), buffer); + if (result) { + memcpy(result->storage(), + CallWithContext(Traits::GetData, input, custom_context), + CallWithContext(Traits::GetSize, input, custom_context)); + } + *output = result; + + CustomContextHelper::TearDown(input, custom_context); + } + + static bool Deserialize(String_Data* input, + UserType* output, + SerializationContext* context) { + if (!input) + return CallSetToNullIfExists(output); + return Traits::Read(StringDataView(input, context), output); + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/string_traits_string16.cc b/mojo/public/cpp/bindings/lib/string_traits_string16.cc new file mode 100644 index 0000000000000000000000000000000000000000..95ff6ccf25769e0d9a4006ca1cf5124041a48134 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/string_traits_string16.cc @@ -0,0 +1,42 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/string_traits_string16.h" + +#include + +#include "base/strings/utf_string_conversions.h" + +namespace mojo { + +// static +void* StringTraits::SetUpContext(const base::string16& input) { + return new std::string(base::UTF16ToUTF8(input)); +} + +// static +void StringTraits::TearDownContext(const base::string16& input, + void* context) { + delete static_cast(context); +} + +// static +size_t StringTraits::GetSize(const base::string16& input, + void* context) { + return static_cast(context)->size(); +} + +// static +const char* StringTraits::GetData(const base::string16& input, + void* context) { + return static_cast(context)->data(); +} + +// static +bool StringTraits::Read(StringDataView input, + base::string16* output) { + return base::UTF8ToUTF16(input.storage(), input.size(), output); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/string_traits_wtf.cc b/mojo/public/cpp/bindings/lib/string_traits_wtf.cc new file mode 100644 index 0000000000000000000000000000000000000000..203f6f590321b18d14b518ff331d83cb5d8c99ca --- /dev/null +++ b/mojo/public/cpp/bindings/lib/string_traits_wtf.cc @@ -0,0 +1,84 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/string_traits_wtf.h" + +#include + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "third_party/WebKit/Source/wtf/text/StringUTF8Adaptor.h" + +namespace mojo { +namespace { + +struct UTF8AdaptorInfo { + explicit UTF8AdaptorInfo(const WTF::String& input) : utf8_adaptor(input) { +#if DCHECK_IS_ON() + original_size_in_bytes = input.charactersSizeInBytes(); +#endif + } + + ~UTF8AdaptorInfo() {} + + WTF::StringUTF8Adaptor utf8_adaptor; + +#if DCHECK_IS_ON() + // For sanity check only. + size_t original_size_in_bytes; +#endif +}; + +UTF8AdaptorInfo* ToAdaptor(const WTF::String& input, void* context) { + UTF8AdaptorInfo* adaptor = static_cast(context); + +#if DCHECK_IS_ON() + DCHECK_EQ(adaptor->original_size_in_bytes, input.charactersSizeInBytes()); +#endif + return adaptor; +} + +} // namespace + +// static +void StringTraits::SetToNull(WTF::String* output) { + if (output->isNull()) + return; + + WTF::String result; + output->swap(result); +} + +// static +void* StringTraits::SetUpContext(const WTF::String& input) { + return new UTF8AdaptorInfo(input); +} + +// static +void StringTraits::TearDownContext(const WTF::String& input, + void* context) { + delete ToAdaptor(input, context); +} + +// static +size_t StringTraits::GetSize(const WTF::String& input, + void* context) { + return ToAdaptor(input, context)->utf8_adaptor.length(); +} + +// static +const char* StringTraits::GetData(const WTF::String& input, + void* context) { + return ToAdaptor(input, context)->utf8_adaptor.data(); +} + +// static +bool StringTraits::Read(StringDataView input, + WTF::String* output) { + WTF::String result = WTF::String::fromUTF8(input.storage(), input.size()); + output->swap(result); + return true; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc b/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc new file mode 100644 index 0000000000000000000000000000000000000000..585a8f094cee722e45faf6c6fa9369b6563618d8 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc @@ -0,0 +1,93 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/sync_call_restrictions.h" + +#if ENABLE_SYNC_CALL_RESTRICTIONS + +#include "base/debug/leak_annotations.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/thread_local.h" +#include "mojo/public/c/system/core.h" + +namespace mojo { + +namespace { + +class SyncCallSettings { + public: + static SyncCallSettings* current(); + + bool allowed() const { + return scoped_allow_count_ > 0 || system_defined_value_; + } + + void IncreaseScopedAllowCount() { scoped_allow_count_++; } + void DecreaseScopedAllowCount() { + DCHECK_LT(0u, scoped_allow_count_); + scoped_allow_count_--; + } + + private: + SyncCallSettings(); + ~SyncCallSettings(); + + bool system_defined_value_ = true; + size_t scoped_allow_count_ = 0; +}; + +base::LazyInstance>::DestructorAtExit + g_sync_call_settings = LAZY_INSTANCE_INITIALIZER; + +// static +SyncCallSettings* SyncCallSettings::current() { + SyncCallSettings* result = g_sync_call_settings.Pointer()->Get(); + if (!result) { + result = new SyncCallSettings(); + ANNOTATE_LEAKING_OBJECT_PTR(result); + DCHECK_EQ(result, g_sync_call_settings.Pointer()->Get()); + } + return result; +} + +SyncCallSettings::SyncCallSettings() { + MojoResult result = MojoGetProperty(MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED, + &system_defined_value_); + DCHECK_EQ(MOJO_RESULT_OK, result); + + DCHECK(!g_sync_call_settings.Pointer()->Get()); + g_sync_call_settings.Pointer()->Set(this); +} + +SyncCallSettings::~SyncCallSettings() { + g_sync_call_settings.Pointer()->Set(nullptr); +} + +} // namespace + +// static +void SyncCallRestrictions::AssertSyncCallAllowed() { + if (!SyncCallSettings::current()->allowed()) { + LOG(FATAL) << "Mojo sync calls are not allowed in this process because " + << "they can lead to jank and deadlock. If you must make an " + << "exception, please see " + << "SyncCallRestrictions::ScopedAllowSyncCall and consult " + << "mojo/OWNERS."; + } +} + +// static +void SyncCallRestrictions::IncreaseScopedAllowCount() { + SyncCallSettings::current()->IncreaseScopedAllowCount(); +} + +// static +void SyncCallRestrictions::DecreaseScopedAllowCount() { + SyncCallSettings::current()->DecreaseScopedAllowCount(); +} + +} // namespace mojo + +#endif // ENABLE_SYNC_CALL_RESTRICTIONS diff --git a/mojo/public/cpp/bindings/lib/sync_event_watcher.cc b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc new file mode 100644 index 0000000000000000000000000000000000000000..b1c97e3691269f69628383fce8ce220023802731 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc @@ -0,0 +1,67 @@ +// 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. + +#include "mojo/public/cpp/bindings/sync_event_watcher.h" + +#include "base/logging.h" + +namespace mojo { + +SyncEventWatcher::SyncEventWatcher(base::WaitableEvent* event, + const base::Closure& callback) + : event_(event), + callback_(callback), + registry_(SyncHandleRegistry::current()), + destroyed_(new base::RefCountedData(false)) {} + +SyncEventWatcher::~SyncEventWatcher() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (registered_) + registry_->UnregisterEvent(event_); + destroyed_->data = true; +} + +void SyncEventWatcher::AllowWokenUpBySyncWatchOnSameThread() { + DCHECK(thread_checker_.CalledOnValidThread()); + IncrementRegisterCount(); +} + +bool SyncEventWatcher::SyncWatch(const bool* should_stop) { + DCHECK(thread_checker_.CalledOnValidThread()); + IncrementRegisterCount(); + if (!registered_) { + DecrementRegisterCount(); + return false; + } + + // This object may be destroyed during the Wait() call. So we have to preserve + // the boolean that Wait uses. + auto destroyed = destroyed_; + const bool* should_stop_array[] = {should_stop, &destroyed->data}; + bool result = registry_->Wait(should_stop_array, 2); + + // This object has been destroyed. + if (destroyed->data) + return false; + + DecrementRegisterCount(); + return result; +} + +void SyncEventWatcher::IncrementRegisterCount() { + register_request_count_++; + if (!registered_) + registered_ = registry_->RegisterEvent(event_, callback_); +} + +void SyncEventWatcher::DecrementRegisterCount() { + DCHECK_GT(register_request_count_, 0u); + register_request_count_--; + if (register_request_count_ == 0 && registered_) { + registry_->UnregisterEvent(event_); + registered_ = false; + } +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc new file mode 100644 index 0000000000000000000000000000000000000000..fd3df396ece35544701e06a72c90b2b769a61202 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc @@ -0,0 +1,135 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/sync_handle_registry.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/threading/thread_local.h" +#include "mojo/public/c/system/core.h" + +namespace mojo { +namespace { + +base::LazyInstance>::Leaky + g_current_sync_handle_watcher = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// static +scoped_refptr SyncHandleRegistry::current() { + scoped_refptr result( + g_current_sync_handle_watcher.Pointer()->Get()); + if (!result) { + result = new SyncHandleRegistry(); + DCHECK_EQ(result.get(), g_current_sync_handle_watcher.Pointer()->Get()); + } + return result; +} + +bool SyncHandleRegistry::RegisterHandle(const Handle& handle, + MojoHandleSignals handle_signals, + const HandleCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (base::ContainsKey(handles_, handle)) + return false; + + MojoResult result = wait_set_.AddHandle(handle, handle_signals); + if (result != MOJO_RESULT_OK) + return false; + + handles_[handle] = callback; + return true; +} + +void SyncHandleRegistry::UnregisterHandle(const Handle& handle) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!base::ContainsKey(handles_, handle)) + return; + + MojoResult result = wait_set_.RemoveHandle(handle); + DCHECK_EQ(MOJO_RESULT_OK, result); + handles_.erase(handle); +} + +bool SyncHandleRegistry::RegisterEvent(base::WaitableEvent* event, + const base::Closure& callback) { + auto result = events_.insert({event, callback}); + DCHECK(result.second); + MojoResult rv = wait_set_.AddEvent(event); + if (rv == MOJO_RESULT_OK) + return true; + DCHECK_EQ(MOJO_RESULT_ALREADY_EXISTS, rv); + return false; +} + +void SyncHandleRegistry::UnregisterEvent(base::WaitableEvent* event) { + auto it = events_.find(event); + DCHECK(it != events_.end()); + events_.erase(it); + MojoResult rv = wait_set_.RemoveEvent(event); + DCHECK_EQ(MOJO_RESULT_OK, rv); +} + +bool SyncHandleRegistry::Wait(const bool* should_stop[], size_t count) { + DCHECK(thread_checker_.CalledOnValidThread()); + + size_t num_ready_handles; + Handle ready_handle; + MojoResult ready_handle_result; + + scoped_refptr preserver(this); + while (true) { + for (size_t i = 0; i < count; ++i) + if (*should_stop[i]) + return true; + + // TODO(yzshen): Theoretically it can reduce sync call re-entrancy if we + // give priority to the handle that is waiting for sync response. + base::WaitableEvent* ready_event = nullptr; + num_ready_handles = 1; + wait_set_.Wait(&ready_event, &num_ready_handles, &ready_handle, + &ready_handle_result); + if (num_ready_handles) { + DCHECK_EQ(1u, num_ready_handles); + const auto iter = handles_.find(ready_handle); + iter->second.Run(ready_handle_result); + } + + if (ready_event) { + const auto iter = events_.find(ready_event); + DCHECK(iter != events_.end()); + iter->second.Run(); + } + }; + + return false; +} + +SyncHandleRegistry::SyncHandleRegistry() { + DCHECK(!g_current_sync_handle_watcher.Pointer()->Get()); + g_current_sync_handle_watcher.Pointer()->Set(this); +} + +SyncHandleRegistry::~SyncHandleRegistry() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // This object may be destructed after the thread local storage slot used by + // |g_current_sync_handle_watcher| is reset during thread shutdown. + // For example, another slot in the thread local storage holds a referrence to + // this object, and that slot is cleaned up after + // |g_current_sync_handle_watcher|. + if (!g_current_sync_handle_watcher.Pointer()->Get()) + return; + + // If this breaks, it is likely that the global variable is bulit into and + // accessed from multiple modules. + DCHECK_EQ(this, g_current_sync_handle_watcher.Pointer()->Get()); + + g_current_sync_handle_watcher.Pointer()->Set(nullptr); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc new file mode 100644 index 0000000000000000000000000000000000000000..f20af56b20a2129f40b22675d822628c50eda409 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc @@ -0,0 +1,76 @@ +// Copyright 2016 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. + +#include "mojo/public/cpp/bindings/sync_handle_watcher.h" + +#include "base/logging.h" + +namespace mojo { + +SyncHandleWatcher::SyncHandleWatcher( + const Handle& handle, + MojoHandleSignals handle_signals, + const SyncHandleRegistry::HandleCallback& callback) + : handle_(handle), + handle_signals_(handle_signals), + callback_(callback), + registered_(false), + register_request_count_(0), + registry_(SyncHandleRegistry::current()), + destroyed_(new base::RefCountedData(false)) {} + +SyncHandleWatcher::~SyncHandleWatcher() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (registered_) + registry_->UnregisterHandle(handle_); + + destroyed_->data = true; +} + +void SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread() { + DCHECK(thread_checker_.CalledOnValidThread()); + IncrementRegisterCount(); +} + +bool SyncHandleWatcher::SyncWatch(const bool* should_stop) { + DCHECK(thread_checker_.CalledOnValidThread()); + IncrementRegisterCount(); + if (!registered_) { + DecrementRegisterCount(); + return false; + } + + // This object may be destroyed during the Wait() call. So we have to preserve + // the boolean that Wait uses. + auto destroyed = destroyed_; + const bool* should_stop_array[] = {should_stop, &destroyed->data}; + bool result = registry_->Wait(should_stop_array, 2); + + // This object has been destroyed. + if (destroyed->data) + return false; + + DecrementRegisterCount(); + return result; +} + +void SyncHandleWatcher::IncrementRegisterCount() { + register_request_count_++; + if (!registered_) { + registered_ = + registry_->RegisterHandle(handle_, handle_signals_, callback_); + } +} + +void SyncHandleWatcher::DecrementRegisterCount() { + DCHECK_GT(register_request_count_, 0u); + + register_request_count_--; + if (register_request_count_ == 0 && registered_) { + registry_->UnregisterHandle(handle_); + registered_ = false; + } +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/template_util.h b/mojo/public/cpp/bindings/lib/template_util.h new file mode 100644 index 0000000000000000000000000000000000000000..5151123ac0b149d90f8bc87e14abe653dab70d89 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/template_util.h @@ -0,0 +1,120 @@ +// Copyright 2014 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_ + +#include + +namespace mojo { +namespace internal { + +template +struct IntegralConstant { + static const T value = v; +}; + +template +const T IntegralConstant::value; + +typedef IntegralConstant TrueType; +typedef IntegralConstant FalseType; + +template +struct IsConst : FalseType {}; +template +struct IsConst : TrueType {}; + +template +struct IsPointer : FalseType {}; +template +struct IsPointer : TrueType {}; + +template +struct EnableIf {}; + +template +struct EnableIf { + typedef T type; +}; + +// Types YesType and NoType are guaranteed such that sizeof(YesType) < +// sizeof(NoType). +typedef char YesType; + +struct NoType { + YesType dummy[2]; +}; + +// A helper template to determine if given type is non-const move-only-type, +// i.e. if a value of the given type should be passed via std::move() in a +// destructive way. +template +struct IsMoveOnlyType { + static const bool value = std::is_constructible::value && + !std::is_constructible::value; +}; + +// This goop is a trick used to implement a template that can be used to +// determine if a given class is the base class of another given class. +template +struct IsSame { + static bool const value = false; +}; +template +struct IsSame { + static bool const value = true; +}; + +template +struct EnsureTypeIsComplete { + // sizeof() cannot be applied to incomplete types, this line will fail + // compilation if T is forward declaration. + using CheckSize = char (*)[sizeof(T)]; +}; + +template +struct IsBaseOf { + private: + static Derived* CreateDerived(); + static char(&Check(Base*))[1]; + static char(&Check(...))[2]; + + EnsureTypeIsComplete check_base_; + EnsureTypeIsComplete check_derived_; + + public: + static bool const value = sizeof Check(CreateDerived()) == 1 && + !IsSame::value; +}; + +template +struct RemovePointer { + typedef T type; +}; +template +struct RemovePointer { + typedef T type; +}; + +template